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

 

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