主要的业务流程 查询和购买
用到的技术:
1.模块化开发 高内聚,低耦合
模块划分
业务模块 公共模块 - 项目模块 - 管理端/销售端
- 按业务层次划分,将dao层和service层单独划分成模块(Entity、Api)
- 功能划分:管理和销售模块(可以独立部署的,算一个应用)
- 重复使用的模块:Util,Quartz,Swagger
构建工具 Gradle
模块:jar/war可以独立部署
创建gradle java项目 使用本地gradle
修改 构建脚本 添加单独文件专门用来依赖管理dependencies.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39ext {
versions = [
springBootVersion: '2.0.0.RELEASE'
]
libs = [
common : [
"org.springframework.boot:spring-boot-starter-web:${versions.springBootVersion}",
"org.springframework.boot:spring-boot-starter-data-jpa:${versions.springBootVersion}",
"org.apache.commons:commons-lang3:3.5"
, "com.h2database:h2:1.4.195"
],
findbugs: [
"com.google.code.findbugs:jsr305:1.3.9"
],
mysql : [
"mysql:mysql-connector-java:5.1.29"
],
jsonrpc:[
"com.github.briandilley.jsonrpc4j:jsonrpc4j:1.5.1"
],
swagger: [
"io.springfox:springfox-swagger2:2.7.0",
"io.springfox:springfox-swagger-ui:2.7.0"
],
hazelcast:[
'com.hazelcast:hazelcast:3.8.6',
'com.hazelcast:hazelcast-spring:3.8.6',
],
activemq:[
"org.springframework.boot:spring-boot-starter-activemq:${versions.springBootVersion}",
],
rsa:[
'commons-codec:commons-codec:1.8'
],
test : [
"org.springframework.boot:spring-boot-starter-test:${versions.springBootVersion}"
]
]
}
修改build.gradle 引入依赖文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23group 'finalLearn'
version '1.0-SNAPSHOT'
apply from: "$rootDir/dependencies.gradle"
subprojects {
apply plugin: 'java'
apply plugin: 'war'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile libs.common
testCompile libs.test
}
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
}
新建模块
1.在工程上新建Module util,
2.将util的build.gradle
清空
因为根项目的build.gradle
的 subprojects
定义过了所有模块。
3.新建其他api,entity,manager,quartz,swagger,saller模块。
4.删除根目录下的src
2数据库设计
创建 manager 数据库 和 saller数据库
管理端:产品表
datetime 保存的时间更广,timestamp有时区信息(读取的时候会根据客户端时区转换成对应时区)
编号varchar50、名称varchar50、收益率decimal5,3、锁定期smallint、状态varchar20、起投金额decimal 15,3、投资步长decimal 15,3、备注
创建时间datetime、创建者varchar20、更新时间datetime、更新者varchar201
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17use manager
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
`Id` varchar(50) NOT NULL DEFAULT '' COMMENT '产品编号',
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '产品名称',
`threshold_amount` decimal(15,3) NOT NULL DEFAULT '0.000' COMMENT '起投金额',
`step_amount` decimal(15,3) NOT NULL DEFAULT '0.000' COMMENT '投资补偿',
`lock_term` smallint(6) NOT NULL DEFAULT '0' COMMENT '锁定期',
`reward_rate` decimal(5,3) NOT NULL DEFAULT '0.000' COMMENT '收益率',
`status` varchar(20) CHARACTER SET latin1 NOT NULL DEFAULT '' COMMENT 'audining审核中',
`memo` varchar(200) DEFAULT NULL,
`create_at` datetime DEFAULT NULL COMMENT '创建时间',
`create_user` varchar(20) DEFAULT NULL,
`update_at` datetime DEFAULT NULL,
`update_user` varchar(20) DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
销售端:
订单表order_t (order是关键字)
订单编号varchar50 渠道编号varchar50 产品编号varchar50 用户编号varchar50 外部订单编号varchar50 类型varchar50 状态varchar50 金额decimal15,3 备注varchar200 创建时间datetime 更新时间datetime1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17create database seller;
use seller;
DROP TABLE IF EXISTS `oreder_t`;
CREATE TABLE `oreder_t` (
`order_id` varchar(50) NOT NULL DEFAULT '',
`ch_id` varchar(50) NOT NULL DEFAULT '' COMMENT '渠道编号',
`product_id` varchar(50) NOT NULL DEFAULT '' COMMENT '产品编号',
`chan_user_id` varchar(50) NOT NULL DEFAULT '',
`order_type` varchar(50) NOT NULL DEFAULT '' COMMENT '状态购买赎回',
`order_status` varchar(50) NOT NULL DEFAULT '' COMMENT '状态初始化处理中成功失败',
`outer_order_id` varchar(50) NOT NULL DEFAULT '' COMMENT '外部订单编号',
`amount` decimal(15,3) NOT NULL DEFAULT '0.000' COMMENT '金额',
`memo` varchar(200) DEFAULT NULL COMMENT '备注',
`create_at` datetime DEFAULT NULL COMMENT '创建时间',
`update_at` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
entity
产品类 Product
entity的java里新建包entity
新建Product
和Order
类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Product {
private String id;
private String name;
private String status;
private BigDecimal thresholdAmount;
private BigDecimal stepAmount;
private Integer lockTerm;
private BigDecimal rewardRate;
private String memo;
private Date createAt;
private Date updateAt;
private String createUser;
private String updateUser;
}
可以用工具类重写toString(apache.commons)
用反射(?)1
2
3
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE);
产品status的枚举类 新建enums包1
2
3
4
5
6
7
8
9
10public enum ProductStatus {
AUDITING("审核中"),
IN_SELL("销售中"),
LOCKED("暂停销售"),
FINISHED("已结束");
ProductStatus(String desc) {
this.desc = desc;
}
private String desc;
}
在Product类中添加说明1
2
3
4/**
* @see entity.enums.ProductStatus
*/
private String status;
订单对象 Order类
1 | "order_t") (name = |
订单状态和订单种类的枚举类1
2
3
4
5
6
7
8
9
10
11public enum OrderType {
APPLY("申购"),
REDEEM("赎回");
private String desc;
OrderType(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
添加doc1
2
3
4
5
6
7
8/**
* @see entity.enums.OrderType
*/
private String orderType;
/**
* @see entity.enums.OrderStatus
*/
private String orderStatus;
1 | public enum OrderStatus { |
3.管理端
Spring Data 用JPA 操作数据库
接口设计 添加产品、查询单个产品、条件查询产品
1.新建启动类
在manager
模块 新建mannager
-ManagerApp.java
启动类
entity不在manager里要手动添加扫描路径1
2
3
4
5
6
7
"entity"}) (basePackages = {
public class ManagerApp {
public static void main(String[] args) {
SpringApplication.run(ManagerApp.class);
}
}
2.配置数据库连接地址:
在resources添加jpa配置文件1
2
3
4
5
6
7
8
9spring:
datasource:
url: jdbc:mysql://192.168.3.109:3306/manager?user=root&password=root&useUnicode=true&characterEncoding=utf-8
jpa:
show-sql: true
server:
servlet:
context-path: /manager
port: 8081
3.新建controller,repository,service包
4.创建接口dao层repository
- ProductRepository
继承 简单查询和复杂查询1
2public interface ProductRepository extends JpaRepository<Product,String>,JpaSpecificationExecutor<Product>{
}
在当前模块的build.gradle
添加entity依赖1
2
3
4
5
6dependencies{
compile project(":entity")
compile project(":util")
compile project(":api")
compile libs.mysql
}
添加产品
5.service
添加产品 参数校验+设置默认值
不应该在实体类有默认值
int会有默认值 Integer不会有默认值
判断整数的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class ProductService {
private static Logger LOG = LoggerFactory.getLogger(ProductService.class);
private ProductRepository repository;
public Product addProduct(Product product){
LOG.debug("创建产品,参数:{}",product);
//数据校验
checkProduct(product);
setDefault(product);
Product rst = repository.save(product);
LOG.debug("创建产品,结果:{}",rst);
return rst;
}
/**
* 产品:编号不可空 步长整数 收益率0-30 校验
* @param product
*/
private void checkProduct(Product product) {
Assert.notNull(product.getId(), "编号不可为空");
Assert.isTrue(BigDecimal.ZERO.compareTo(product.getRewardRate())<0&&BigDecimal.valueOf(30).compareTo(product.getRewardRate())>=0,"收益率范围错误" );
Assert.isTrue(BigDecimal.valueOf(product.getStepAmount().longValue()).compareTo(product.getStepAmount())==0, "投资步长需为整数");
}
/**
* 产品默认值:创建更新时间,步长,状态,锁定期
* @param product
*/
public void setDefault(Product product) {
if(product.getCreateAt()==null){
product.setCreateAt(new Date());
}
if(product.getUpdateAt()==null){
product.setUpdateAt(new Date());
}
if(product.getStepAmount()==null){
product.setStepAmount(BigDecimal.ZERO);
}
if(product.getLockTerm()== null){
product.setLockTerm(0);
}
if(product.getStatus()==null){
product.setStatus(ProductStatus.AUDITING.name());
}
}
}
6.controller info级别的log1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"/products") (
public class ProductController {
private static Logger LOG = LoggerFactory.getLogger(ProductController.class);
private ProductService service;
"",method= RequestMethod.POST) (value =
public Product addProduct(@RequestBody Product product){
LOG.info("创建产品,参数:{}",product);
Product rst = service.addProduct(product);
LOG.info("创建产品,参数:{}",product);
return rst;
}
}
查询产品单个产品
7.service1
2
3
4
5
6
7public Product findOne(String id){
Assert.notNull(id,"需要产编号参数");
LOG.debug("查询单个产品,id={}",id);
Product product = repository.findById(id).orElse(null);
LOG.debug("查询单个产品,结果={}",product);
return product;
}
8.controller从URL里获得产品id@PathVariable
1
2
3
4
5
6
7"/{id}",method = RequestMethod.GET) (value =
public Product findOne(@PathVariable String id){
LOG.info("查询单个产品,id:{}",id);
Product product = service.findOne(id);
LOG.info("查询单个产品,结果:{}",product);
return product;
}
复杂查询 分页查询
9.service
1).多个编号查询List<String> idList
2).收益率范围查询BigDecimal minRewardRate, BigDecimal maxRewardRate
3).多个状态查询List<String> statusList
4).分页查询Pageable pageable
分页参数
定义Specification
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public Page<Product> query(List<String> idList,
BigDecimal minRewardRate, BigDecimal maxRewardRate,
List<String> statusList,
Pageable pageable){
LOG.debug("查询产品,idList={},min={},max:{},status={},pageable={}",idList,minRewardRate,maxRewardRate,statusList,pageable);
Specification<Product> specification = new Specification<Product>() {
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//获得编号、收益率、状态列
Expression<String> idCol = root.get("id");
Expression<BigDecimal> rewardRateCol = root.get("rewardRate");
Expression<String> statusCol = root.get("status");
//断言列表
List<Predicate> predicates = new ArrayList<>();
if(idList!=null &&idList.size()>0){
predicates.add(idCol.in(idList));
}
if(minRewardRate!=null&&BigDecimal.ZERO.compareTo(minRewardRate)<0){
predicates.add(cb.ge(rewardRateCol,minRewardRate));
}
if(maxRewardRate!=null&&BigDecimal.ZERO.compareTo(maxRewardRate)<0){
predicates.add(cb.le(rewardRateCol,maxRewardRate));
}
if(statusList!=null&&statusList.size()>0){
predicates.add(statusCol.in(statusList));
}
// 查询 列表->数组?
query.where(predicates.toArray(new Predicate[0]));
return null;
}
};
Page<Product> page = repository.findAll(specification, pageable);
LOG.debug("查询产品,结果={}",page);
return page;
}
10.controller
pageNum 哪页 pageSize 每页多少1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16"",method = RequestMethod.GET) (value =
public Page<Product> query(String ids, BigDecimal minRewardRate,BigDecimal maxRewardRate,String status,@RequestParam(defaultValue = "0") int pageNum,@RequestParam(defaultValue = "10")int pageSize){
LOG.info("查询产品,ids={},min={},max={},status={},pagenum ={},pagesize={}", ids,minRewardRate,maxRewardRate,status,pageNum,pageSize);
List<String> idList = null,statusList = null;
if(!StringUtils.isEmpty(ids)){
idList = Arrays.asList(ids.split(","));
}
if(!StringUtils.isEmpty(status)){
statusList = Arrays.asList(status.split(","));
}
Pageable pageable = new PageRequest(pageNum,pageSize);
Page<Product> page = service.query(idList, minRewardRate, maxRewardRate, statusList, pageable);
LOG.info("={}",page );
return page;
}
API测试
启动MangerAPP1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var data = {
"id":"001",
"name":"金融1号",
"thresholdAmount":10,
"stepAmount":1,
"lockTerm":0,
"rewardRate":3.86,
"status":"AUDITING"
}
fetch("http://localhost:8081/manager/products",{ // post请求
method:"POST",
headers: {
"Content-Type": "application/json"
},
body:JSON.stringify(data)
}).then(res => console.log(res.json()))
.catch(e=>console.log("something went wrong: " + e))
查看数据库 OK
查询
get:http://localhost:8081/manager/products/001
jackson时间格式化 好像并不用1
2
3
4spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
post:http://localhost:8081/manager/products/1
2
3
4
5
6
7
8{
"name":"金融1号",
"thresholdAmount":10,
"stepAmount":1,
"lockTerm":0,
"rewardRate":3.86,
"status":"AUDITING"
}
返回1
2
3
4
5
6
7{
"timestamp": "2018-08-29 19:38:21",
"status": 500,
"error": "Internal Server Error",
"message": "编号不可为空",
"path": "/manager/products/"
}
4.统一错误处理
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling
spring boot提供了默认的/error
全局错误处理
对于machine clients(程序,jar包,http client ,ajax发起的), it produces a JSON response 包括HTTP status, the exception message.
对于浏览器(browser clients会自动添加accept: text/html到报文头)发起的请求renders the same data in HTML format 如果用postman加上Accept:text/html
就返回html
通过实现ErrorController
接口注册到容器中或者添加一个ErrorAttributes
bean
还可以用@ControllerAdvice
拦截controller的错误
源码:变成html的实现方法BasicErrorController
1 |
|
错误页面的拦截
所以可以通过修改server.error.path修改默认配置1
2
3
4
5
6
7
8
9
10
11
12"text/html") (produces =
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// 自定义的error视图
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//如果没有视图,用error视图
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
普通机器客户端的拦截1
2
3
4
5
6
7
8
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
都是用getErrorAttributes
获取响应数据,用getStatus
获取状态码
BasicErrorController
的注册过程 右键Find Usages1
2
3
4
5
6
7
//条件表达式,如果没有则新建
(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
html错误形式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"server.error.whitelabel", name = "enabled", matchIfMissing = true) (prefix =
(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
private final SpelView defaultErrorView = new SpelView(
"<html><body><h1>Whitelabel Error Page</h1>"
+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
+ "<div id='created'>${timestamp}</div>"
+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
+ "<div>${message}</div></body></html>");
//注册了一个View名字叫error
"error") (name =
"error") (name =
public View defaultErrorView() {
return this.defaultErrorView;
}
// If the user adds @EnableWebMvc then the bean name view resolver from
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
(BeanNameViewResolver.class)
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
自定义错误处理
新建错误页面1
2
3
4
5
6
7
8
9src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
方法1 拦截5xx错误 新建resources-static.error-5xx.html
方法2新建error包 新建类
1.继承BasicErrorController
重写返回错误的方法,去掉不要的信息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class MyErrorController extends BasicErrorController{
public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorProperties, errorViewResolvers);
}
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(request, includeStackTrace);
errorAttributes.remove("timestamp");
errorAttributes.remove("error");
errorAttributes.remove("path");
return errorAttributes;
}
}
2.添加配置类注册bean 参考BasicError的注册类ErrorMvcAutoConfiguration 将Controller注册到容器中1
2
3
4
5
6
7
8
9
public class ErrorConfiguration {
public MyErrorController basicErrorController(ErrorAttributes errorAttributes,
ServerProperties serverProperties,
ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
return new MyErrorController(errorAttributes, serverProperties.getError(),errorViewResolversProvider.getIfAvailable());
}
}
效果:1
2
3{
"message": "编号不可空",
}
3.添加错误枚举类(注意 枚举类中的类方法)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public enum ErrorEnum {
ID_NOT_NULL("F001","编号不可空",false),
UNKNOWN("999","未知异常",false);
private String code;
private String message;
private boolean canRetry;
ErrorEnum(String code, String message, boolean canRetry) {
this.code = code;
this.message = message;
this.canRetry = canRetry;
}
public static ErrorEnum getByCode(String code){
for(ErrorEnum errorEnum:ErrorEnum.values()){
if(errorEnum.code.equals(code)){
return errorEnum;
}
}
return UNKNOWN;
}
修改ProductService中的check抛出的不是message而是自己定义的错误code1
2private void checkProduct(Product product) {
Assert.notNull(product.getId(), ErrorEnum.ID_NOT_NULL.getCode());}
修改MyErrorController,添加errorcode和retry1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(request, includeStackTrace);
errorAttributes.remove("timestamp");
errorAttributes.remove("error");
errorAttributes.remove("path");
String message = (String)errorAttributes.get("message");
errorAttributes.remove("message");
// 获取错误种类
ErrorEnum errorEnum = ErrorEnum.getByCode(message);
errorAttributes.put("message",errorEnum.getMessage() );
errorAttributes.put("code",errorEnum.getCode() );
errorAttributes.put("canRetry",errorEnum.isCanRetry() );
return errorAttributes;
}
输出1
2
3
4
5{
"message": "编号不可空",
"code": "F001",
"canRetry": false
}
方法3:@ControllerAdvice
优先级比方法2高,controller异常后直接这个然后返回response
扫描制定的package1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18"manager.controller"}) (basePackages = {
public class ErrorControllerAdvice {
(Exception.class)
public ResponseEntity handleException(Exception e){
Map<String, Object> errorAttributes = new HashMap<>();
String errorcode = e.getMessage();
ErrorEnum errorEnum = ErrorEnum.getByCode(errorcode);
errorAttributes.put("message",errorEnum.getMessage() );
errorAttributes.put("code",errorEnum.getCode() );
errorAttributes.put("canRetry",errorEnum.isCanRetry() );
//这里再抛一个异常就到basicerror里了
Assert.isNull(errorAttributes,"advice" );
errorAttributes.put("type","advice");
return new ResponseEntity(errorAttributes, HttpStatus.INTERNAL_SERVER_ERROR);
}
比方法二更优先拦截,在ErrorControllerAdvice
里的错误会到BasicErrorController
1 | { |
5.自动化测试 不是写细到方法的单元测试 而是直接功能测试http请求
1.添加依赖 会自动导入JUnit包1
2
3test : [
"org.springframework.boot:spring-boot-starter-test:${versions.springBootVersion}"
]
新建测试类test-manager.controller-ProductControllerTest
测试创建产品
POST请求工具类
为方便发送POST请求的工具类 放在util模块,
并把util模块添加到manager1
compile project(":util")
1.JsonUtil
:Object->Json 用于log输出1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class JsonUtil {
private static final Logger LOG = LoggerFactory.getLogger(JsonUtil.class);
private final static ObjectMapper mapper = new ObjectMapper();
public static String toJson(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (IOException e) {
LOG.error("to json exception.", e);
throw new JSONException("把对象转换为JSON时出错了", e);
}
}
}
final class JSONException extends RuntimeException {
public JSONException(final String message) {super(message);}
public JSONException(final String message, final Throwable cause) {super(message, cause);}
}
2.RestUtil
用RestTemplate
发送Http请求用的是ClientHttpRequest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public class RestUtil {
static Logger log = LoggerFactory.getLogger(RestUtil.class);
public static HttpEntity<String> makePostJSONEntiry(Object param) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<String> formEntity = new HttpEntity<String>(
JsonUtil.toJson(param), headers);
log.info("rest-post-json-请求参数:{}", formEntity.toString());
return formEntity;
}
public static <T> T postJSON(RestTemplate restTemplate, String url, Object param, Class<T> responseType) {
HttpEntity<String> formEntity = makePostJSONEntiry(param);
T result = restTemplate.postForObject(url, formEntity, responseType);
log.info("rest-post-json 响应信息:{}", JsonUtil.toJson(result));
return result;
}
}
添加产品测试
正常数据的测试1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 (SpringRunner.class)
//随机端口
(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ProductControllerTest {
private static RestTemplate rest =new RestTemplate();
"http://localhost:${local.server.port}/manager") (
private String baseUrl;
//正常的测试用例
private static List<Product> normals = new ArrayList<>();
//异常测试用例
private static List<Product> exceptions = new ArrayList<>();
// 只执行一次
public static void init(){
Product p1 = new Product("T011", "灵活宝1号", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(10), BigDecimal.valueOf(1), BigDecimal.valueOf(3.42));
Product p2 = new Product("T012", "活期盈-金色人生", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(10), BigDecimal.valueOf(0), BigDecimal.valueOf(3.28));
Product p3 = new Product("T013", "朝朝盈-聚财", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(100), BigDecimal.valueOf(10), BigDecimal.valueOf(3.86));
normals.add(p1);
normals.add(p2);
normals.add(p3);
// 如果有插入时间则插入成功
public void create(){
normals.forEach(product -> {
Product rst = RestUtil.postJSON(rest, baseUrl + "/products", product, Product.class);
Assert.notNull(rst.getCreateAt(),"插入失败" );
});
}
}
异常数据的测试 返回500 是RestUtil报错返回5001
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Product e1 = new Product(null, "编号空", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(10), BigDecimal.valueOf(1), BigDecimal.valueOf(3.42));
Product e2 = new Product("E002", "收益>30", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(10), BigDecimal.valueOf(0), BigDecimal.valueOf(31));
Product e3 = new Product("E003", "投资步长是小数", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(100), BigDecimal.valueOf(1.01), BigDecimal.valueOf(3.86));
exceptions.add(e1);
exceptions.add(e2);
exceptions.add(e3);
public void createError(){
exceptions.forEach(product -> {
Product rst = RestUtil.postJSON(rest, baseUrl + "/products", product, Product.class);
Assert.notNull(rst.getCreateAt(),"插入失败" );
});
}
init
对错误的测试用例添加异常捕获1
2
3
4
5
6
7
8
9
10
11ResponseErrorHandler errorHandler = new ResponseErrorHandler() {
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
public void handleError(ClientHttpResponse response) throws IOException {
}
};
rest.setErrorHandler(errorHandler);
1 |
|
out:{canRetry=false, code=F001, message=编号不可空, type=advice}