Spring boot理財系統2 管理端

添加產品

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;
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章