添加产品
1、管理端启动类
package com.qwl.manager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 管理端启动类
*/
@SpringBootApplication
public class ManagerApp {
public static void main(String[] args) {
SpringApplication.run(ManagerApp.class);
}
}
2、数据库的连接配置application.yml
spring:
datasource:
url: jdbc:mysql:///manager?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
jpa:
show-sql: true
server:
servlet:
context-path: /manager
port: 8080
3、ProductRepository接口
build.gradle中添加
dependencies {
compile project(":entity")
}
package com.qwl.manager.repositories;
import com.qwl.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product,String> {
}
4、ProductService产品服务类
package com.qwl.manager.service;
import com.qwl.entity.Product;
import com.qwl.entity.enums.ProductStatus;
import com.qwl.manager.repositories.ProductRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import java.math.BigDecimal;
import java.util.Date;
@Service
public class ProductService {
private static Logger LOG = (Logger) LoggerFactory.getLogger(ProductService.class);
@Autowired
private ProductRepository repository;
public Product addProduct(Product product){
//打印日信息
LOG.debug("创建产品,参数:{}",product);
//数据校验
checkProduct(product);
//设置默认值
setDefault(product);
//保存值
Product result = repository.save(product);
LOG.debug("创建产品,结果:{}",result);
return result;
}
/*
设置默认值:
创建时间、更新时间、投资步长、锁定期
*/
private 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());
}
}
/*
产品数据的校验:
1.非空数据
2.收益率在0-30%以内
3.投资步长需要为整数
*/
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, "投资步长需为整数");
}
}
5、ProductController产品控制类
package com.qwl.manager.controller;
import com.qwl.entity.Product;
import com.qwl.manager.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 产品
*/
@RestController
@RequestMapping("/products")
public class ProductController {
private static Logger LOG =(Logger) LoggerFactory.getLogger(ProductService.class);
@Autowired
private ProductService productService;
public Product addProduct(@RequestBody Product product){
LOG.debug("创建产品,参数:{}",product);
Product result = productService.addProduct(product);
LOG.info("创建产品,结果:{}",result);
return result;
}
}
查询产品
1、查询单个产品
在ProductService中添加
/**
* 查询单个产品
*/
public Product findOne(String id){
Assert.notNull(id,"需要产品编号参数");
LOG.debug("查询单个产品,id={}",id);
Product product = repository.findOne(id);
LOG.debug("查询单个产品,结果={}",product);
return product;
}
在ProductController中添加
/**
* 查询单个产品
*/
@RequestMapping(value = "/{id}",method = RequestMethod.GET )
public Product findOne(@PathVariable String id){
LOG.info("查询单个产品,id={}",id);
Product product = service.findOne(id);
LOG.info("查询单个产品,结果={}",product);
return product;
}
2、分页查询
在ProductService中添加
/**
* 分页查询
*/
public Page<Product> query(List<String> idList,
BigDecimal minRewardRate, BigDecimal maxRewardRate,
List<String> statusList,
Pageable pageable){
LOG.debug("查询产品,idList={},minRewardRate={},maxRewardRate={},statusList={},pageable={}",idList,minRewardRate,maxRewardRate,statusList,pageable);
Specification<Product> specification = new Specification<Product>() {
@Override
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 (BigDecimal.ZERO.compareTo(minRewardRate) < 0){
predicates.add(cb.ge(rewardRateCol,minRewardRate));
}
if (BigDecimal.ZERO.compareTo(minRewardRate) < 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;
}
在ProductController中添加
/**
* 分页查询
*/
@RequestMapping(value = "",method = RequestMethod.GET)
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={},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;
}
测试
添加驱动依赖
dependencies {
compile project(":entity")
compile libs.mysql
}
在管理端启动类上添加@EntityScan(basePackages = {"com.qwl.entity"})
统一错误处理
用户友好的错误说明
统一处理,简化业务代码
异常标准化
自定义格式化时间,在manager模块下的application.yml中添加
自定义静态错误页面
此时,启动项目,如果项目抛出 5xx 请求错误,就会自动展示 5xx.html 这个页面
自定义异常数据
定义MyErrorController类
package com.qwl.manager.error;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* 自定义错误处理controller
*/
public class MyErrorController extends BasicErrorController {
public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorProperties, errorViewResolvers);
}
@Override
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
Map<String, Object> attrs = super.getErrorAttributes(request, includeStackTrace);
//去除无关的属性
attrs.remove("timestamp");
attrs.remove("status");
attrs.remove("error");
attrs.remove("exception");
attrs.remove("path");
return attrs;
}
}
定义ErrorConfiguration类 错误处理相关配置
package com.qwl.manager.error;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.*;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 错误处理相关配置
*/
@Configuration
public class ErrorConfiguration {
@Bean
public MyErrorController basicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
return new MyErrorController(errorAttributes, serverProperties.getError(),
errorViewResolversProvider.getIfAvailable());
}
}
定义ErrorEnum类 错误种类
package com.uos.manager.error;
/**
* 错误种类
*/
public 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;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
public boolean isCanRetry() {
return canRetry;
}
}
在MyErrorController中添加相关属性
// 获取到erorCode
String errorCode =(String) attrs.get("message");
//获取错误种类
ErrorEnum errorEnum = ErrorEnum.getByCode(errorCode);
//添加相关属性
attrs.put("message",errorEnum.getMessage());
attrs.put("code",errorEnum.getCode());
attrs.put("canRetry",errorEnum.isCanRetry());
return attrs;
新建ErrorControllerAdvice类
package com.qwl.manager.error;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.beans.Expression;
import java.util.HashMap;
import java.util.Map;
/**
* 统一错误处理
*/
@ControllerAdvice(basePackages = {"com.qwl.manager.controller"})
public class ErrorControllerAdvice {
@ExceptionHandler(Exception.class)//用来指明异常的处理类型
@ResponseBody
public ResponseEntity handleException(Exception e) {
Map<String, Object> attrs = new HashMap<>();
String errorCode = e.getMessage();
ErrorEnum errorEnum = ErrorEnum.getByCode(errorCode);
//添加相关属性
attrs.put("message", errorEnum.getMessage());
attrs.put("code", errorEnum.getCode());
attrs.put("canRetry", errorEnum.isCanRetry());
attrs.put("type", "advice");
return new ResponseEntity(attrs, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
说明:ControllerAdvice是Controller的增强
(四)、自动化测试
采用功能测试,使用Junit框架
在util下创建JsonUtil类
package com.qwl.util;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.DateFormat;
public class JsonUtil {
private static final Logger LOG = LoggerFactory.getLogger(JsonUtil.class);
private final static ObjectMapper mapper = new ObjectMapper();
static {
mapper.enable(SerializationFeature.WRITE_NULL_MAP_VALUES);
mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
// 属性可见度只打印public
//mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
public static void setDateFormat(DateFormat dateFormat) {
mapper.setDateFormat(dateFormat);
}
/**
* 把Java对象转为JSON字符串
*
* @param obj the object need to transfer into json string.
* @return json string.
*/
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);
}
}
在util下创建创建RestUtil类
package com.qwl.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class RestUtil {
static Logger log = LoggerFactory.getLogger(RestUtil.class);
/**
* 发送post 请求
*
* @param restTemplate
* @param url
* @param param
* @param responseType
* @param <T>
* @return
*/
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;
}
/**
* 生成json形式的请求头
*
* @param param
* @return
*/
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 HttpEntity<String> makePostTextEntiry(Map<String, ? extends Object> param) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<String> formEntity = new HttpEntity<String>(
makeGetParamContent(param), headers);
log.info("rest-post-text-请求参数:{}", formEntity.toString());
return formEntity;
}
/**
* 生成Get请求内容
*
* @param param
* @param excluedes
* @return
*/
public static String makeGetParamContent(Map<String, ? extends Object> param, String... excluedes) {
StringBuilder content = new StringBuilder();
List<String> excludeKeys = Arrays.asList(excluedes);
param.forEach((key, v) -> {
content.append(key).append("=").append(v).append("&");
});
if (content.length() > 0) {
content.deleteCharAt(content.length() - 1);
}
return content.toString();
}
}
编写ProductControllerTest类
测试添加产品
package com.qwl.manager.controller;
import com.uos.entity.Product;
import com.uos.entity.enums.ProductStatus;
import com.uos.util.RestUtil;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ProductControllerTest {
private static RestTemplate rest = new RestTemplate();
@Value("http://localhost:${local.server.port}/manager")
private String baseUrl;
//测试用例
//正常产品数据
private static List<Product> normals = new ArrayList<>();
//异常产品数据
private static List<Product> exceptions = new ArrayList<>();
@BeforeClass
public static void init(){
Product p1 = new Product("T001", "灵活宝1号", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(10), BigDecimal.valueOf(1), BigDecimal.valueOf(3.42));
Product p2 = new Product("T002", "活期盈-金色人生", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(10), BigDecimal.valueOf(0), BigDecimal.valueOf(3.28));
Product p3 = new Product("T003", "朝朝盈-聚财", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(100), BigDecimal.valueOf(10), BigDecimal.valueOf(3.86));
normals.add(p1);
normals.add(p2);
normals.add(p3);
Product e1 = new Product(null, "编号不可为空", ProductStatus.AUDITING.name(),
BigDecimal.valueOf(10), BigDecimal.valueOf(1), BigDecimal.valueOf(2.34));
Product e2 = new Product("E002", "收益率范围错误", ProductStatus.AUDITING.name(),
BigDecimal.ZERO, BigDecimal.valueOf(1), BigDecimal.valueOf(31));
Product e3 = new Product("E003", "投资步长需为整数", ProductStatus.AUDITING.name(),
BigDecimal.ZERO, BigDecimal.valueOf(1.01), BigDecimal.valueOf(3.44));
exceptions.add(e1);
exceptions.add(e2);
exceptions.add(e3);
ResponseErrorHandler errorHandler = new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
};
rest.setErrorHandler(errorHandler);
}
@Test
public void create(){
normals.forEach(product -> {
Product result = RestUtil.postJSON(rest,baseUrl+"/products",product,Product.class);
Assert.notNull(result.getCreateAt(),"创建失败");
}
);
}
@Test
public void createException(){
exceptions.forEach(product -> {
Map<String,String> result = RestUtil.postJSON(rest,baseUrl+"/products",product, HashMap.class);
Assert.isTrue(result.get("message").equals(product.getName()), "插入成功");
}
);
}
}
@Test
public void findOne() {
normals.forEach(product -> {
Product result = rest.getForObject(baseUrl + "/products/" + product.getId(), Product.class);
Assert.isTrue(result.getId().equals(product.getId()), "查询失败");
});
exceptions.forEach(product -> {
Product result = rest.getForObject(baseUrl + "/products/" + product.getId(), Product.class);
Assert.isNull(result, "查询失败");
});
}
product要加
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createAt;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateAt;
public Product() {
}
public Product(String id, String name, String status, BigDecimal thresholdAmount, BigDecimal stepAmount, BigDecimal rewardRate) {
this.id = id;
this.name = name;
this.status = status;
this.thresholdAmount = thresholdAmount;
this.stepAmount = stepAmount;
this.rewardRate = rewardRate;
}