添加產品
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;
}