spring boot项目实战之理财系统

springboot项目实战之理财产品系统

 

项目完整代码下载地址: https://gitee.com/chuzhuyong/financial-project/tree/master

 

一、项目准备

模块化开发的好处

  高内聚,低耦合

  并行开发,提高开发的效率

如何划分?

  业务层次

  功能划分

  重复使用

 

 

 (一)、工程创建

构建工具:gradle

(1)下载网址:https://gradle.org/

spring boot项目实战之理财系统_第1张图片

(2)系统环境变量配置

 spring boot项目实战之理财系统_第2张图片

 

spring boot项目实战之理财系统_第3张图片

 

 

(3)使用gradle -v命令查看配置是否成功

 spring boot项目实战之理财系统_第4张图片

 

 

 

使用IDEA创建项目流程

 spring boot项目实战之理财系统_第5张图片

 

 

     spring boot项目实战之理财系统_第6张图片

 

    spring boot项目实战之理财系统_第7张图片

 

创建模块

    spring boot项目实战之理财系统_第8张图片

 

 

(二)、数据库设计

管理端                     

             产品表

spring boot项目实战之理财系统_第9张图片

销售端:

                      订单表

 

spring boot项目实战之理财系统_第10张图片

 

(三)、创建产品表

create table product(

id VARCHAR(50) not null comment '产品编号',

name VARCHAR(50) not null comment '产品名称',

threshold_amount DECIMAL(15,3) not null comment '起步金额',

step_amount DECIMAL(15,3) not null comment '投资步长',

lock_term SMALLINT not null comment '锁定期',

reward_rate DECIMAL(15,3) not null comment '收益率,0-100 百分比值',

status VARCHAR(20) not null comment '状态,AUDINTING:审核中,IN_SELL:销售 中,LOCKED:暂停销售,FINISHED:已结束',

memo VARCHAR(200) comment '备注',

create_at datetime comment '创建时间',

create_user VARCHAR(20) comment '创建者',

update_at datetime comment '更新时间',

update_user VARCHAR(20) comment '更新者',

PRIMARY KEY(id)

)ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

  spring boot项目实战之理财系统_第11张图片

 

 

  

(四)、创建订单表

create table order_t(

order_id VARCHAR(50) not null comment '订单编号',

chan_id VARCHAR(50) not null comment '渠道编号',

product_id VARCHAR(50) not null comment '产品编号',

chan_user_id VARCHAR(50) not null comment '渠道用户编号',

order_type VARCHAR(50) not null comment '类型,APPLY:申购,REDEEM:赎回',

order_status VARCHAR(50) not null comment '状态,INIT:初始化,PROCESS:处理 中,SUCCESS:成功,FAIL:失败',

outer_order_id VARCHAR(50) not null comment '外部订单编号',

amount DECIMAL(15,3) not null comment '金额',

memo VARCHAR(200) comment '备注',

create_at datetime comment '创建时间',

update_at datetime comment '更新时间',

PRIMARY KEY(order_id)

)ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

spring boot项目实战之理财系统_第12张图片

 

 

(五)、Entity模块

Product类

package com.uos.entity;

import java.math.BigDecimal;
import java.util.Date;

/**
 * 产品
 */
@Entity
public class Product {
    @Id
    private String id;
    private String name;
    private String status;
    //起投金额
    private BigDecimal threshouldAmount;
    //投资步长
    private BigDecimal stepAmount;
    //锁定期
    private Integer lockTerm;
    //收益率
    private BigDecimal rewardRate;
    private String memo;
    private Date createAt;
    private Date updateAt;
    private String createUser;
    private String updateUser;

    @Override
    public String toString() {
        return "Order{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", status='" + status + '\'' +
                ", threshouldAmount=" + threshouldAmount +
                ", stepAmount=" + stepAmount +
                ", lockTerm=" + lockTerm +
                ", rewardRate=" + rewardRate +
                ", memo='" + memo + '\'' +
                ", createAt=" + createAt +
                ", updateAt=" + updateAt +
                ", createUser='" + createUser + '\'' +
                ", updateUser='" + updateUser + '\'' +
                '}';
    }


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public BigDecimal getThreshouldAmount() {
        return threshouldAmount;
    }

    public void setThreshouldAmount(BigDecimal threshouldAmount) {
        this.threshouldAmount = threshouldAmount;
    }

    public BigDecimal getStepAmount() {
        return stepAmount;
    }

    public void setStepAmount(BigDecimal stepAmount) {
        this.stepAmount = stepAmount;
    }

    public Integer getLockTerm() {
        return lockTerm;
    }

    public void setLockTerm(Integer lockTerm) {
        this.lockTerm = lockTerm;
    }

    public BigDecimal getRewardRate() {
        return rewardRate;
    }

    public void setRewardRate(BigDecimal rewardRate) {
        this.rewardRate = rewardRate;
    }

    public String getMemo() {
        return memo;
    }

    public void setMemo(String memo) {
        this.memo = memo;
    }

    public Date getCreateAt() {
        return createAt;
    }

    public void setCreateAt(Date createAt) {
        this.createAt = createAt;
    }

    public Date getUpdateAt() {
        return updateAt;
    }

    public void setUpdateAt(Date updateAt) {
        this.updateAt = updateAt;
    }

    public String getCreateUser() {
        return createUser;
    }

    public void setCreateUser(String createUser) {
        this.createUser = createUser;
    }

    public String getUpdateUser() {
        return updateUser;
    }

    public void setUpdateUser(String updateUser) {
        this.updateUser = updateUser;
    }
}

 

Order类

package com.uos.entity;

import java.math.BigDecimal;
import java.util.Date;

/**
 * 订单
 */
@Entity(name = "order_t")
public class Order {
    @Id
    private String orderId;
    //渠道id
    private String chanId;
    private String chanUserId;
    private String orderType;
    private String productId;
    private BigDecimal amount;
    private String outerOrderId;
    private String orderStatus;
    private String memo;
    private Date createAt;
    private Date updateAt;

    @Override
    public String toString() {
        return "Order{" +
                "orderId='" + orderId + '\'' +
                ", chanId='" + chanId + '\'' +
                ", chanUserId='" + chanUserId + '\'' +
                ", orderType='" + orderType + '\'' +
                ", productId='" + productId + '\'' +
                ", amount=" + amount +
                ", outerOrderId='" + outerOrderId + '\'' +
                ", orderStatus='" + orderStatus + '\'' +
                ", memo='" + memo + '\'' +
                ", createAt=" + createAt +
                ", updateAt=" + updateAt +
                '}';
    }

    public String getOrderId() {
        return orderId;
    }
    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
    public String getChanId() {
        return chanId;
    }
    public void setChanId(String chanId) {
        this.chanId = chanId;
    }
    public String getChanUserId() {
        return chanUserId;
    }
    public void setChanUserId(String chanUserId) {
        this.chanUserId = chanUserId;
    }

    public String getOrderType() {
        return orderType;
    }
    public void setOrderType(String orderType) {
        this.orderType = orderType;
    }
    public String getProductId() {
        return productId;
    }
    public void setProductId(String productId) {
        this.productId = productId;
    }
    public BigDecimal getAmount() {
        return amount;
    }
    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }
    public String getOuterOrderId() {
        return outerOrderId;
    }
    public void setOuterOrderId(String outerOrderId) {
        this.outerOrderId = outerOrderId;
    }
    public String getOrderStatus() {
        return orderStatus;
    }
    public void setOrderStatus(String orderStatus) {
        this.orderStatus = orderStatus;
    }
    public String getMemo() {
        return memo;
    }
    public void setMemo(String memo) {
        this.memo = memo;
    }

    public Date getCreateAt() {
        return createAt;
    }

    public void setCreateAt(Date createAt) {
        this.createAt = createAt;
    }

    public Date getUpdateAt() {
        return updateAt;
    }
    public void setUpdateAt(Date updateAt) {
        this.updateAt = updateAt;
    }
}

 

二、管理端

 

(一) 、添加产品

1、管理端启动类

package com.uos.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://127.0.0.1:3306/manager?user=root&password=123456&useUnicode=true&characterEncoding=utf-8
  jpa:
    show-sql: true
server:
  context-path: /manager
  port: 8081

3、ProductRepository接口

package com.uos.manager.repositories;

import com.uos.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;


/**
 * 产品管理
 */
public interface ProductRepository extends JpaRepository,JpaSpecificationExecutor {

}

4、ProductService产品服务类

package com.uos.manager.service;

import com.uos.entity.Product;
import com.uos.entity.enums.ProductStatus;
import com.uos.manager.repositories.ProductRepository;
import org.slf4j.Logger;

import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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.uos.manager.controller;

import com.uos.entity.Product;
import com.uos.manager.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 产品
 */
@RestController
@RequestMapping("/products")
public class ProductController {
    private static Logger LOG = (Logger) LoggerFactory.getLogger(ProductService.class);
    @Autowired
    private ProductService service;
    @RequestMapping(value = "",method = RequestMethod.POST)
    public Product addProduct(@RequestBody Product product){
        LOG.info("创建产品,参数:{}",product);
        Product result = service.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 query(List idList,
                           BigDecimal minRewardRate, BigDecimal maxRewardRate,
                           List statusList,
                           Pageable pageable){
    LOG.debug("查询产品,idList={},minRewardRate={},maxRewardRate={},statusList={},pageable={}",idList,minRewardRate,maxRewardRate,statusList,pageable);

    Specification specification = new Specification() {
        @Override
        public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
            //获取相关的列
            Expression idCol = root.get("id");
            Expression rewardRateCol = root.get("rewardRate");
            Expression statusCol = root.get("status");
            //定义断言列表,用来生成断言
            List 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 page = repository.findAll(specification,pageable);
    LOG.debug("查询产品,结果={}",page);
    return page;
}

在ProductController中添加

/**
 * 分页查询
 */
@RequestMapping(value = "",method = RequestMethod.GET)
public Page 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 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 page = service.query(idList,minRewardRate,maxRewardRate,statusList,pageable);
    LOG.info("查询产品,结果={}",page);
    return page;
}

在管理端启动类上添加@EntityScan(basePackages = {"com.uos.entity"})

 

(三)、统一错误处理

  用户友好的错误说明

  统一处理,简化业务代码

  异常标准化

 

自定义格式化时间,在manager模块下的application.yml中添加

spring boot项目实战之理财系统_第13张图片

 

 

 自定义错误页面

spring boot项目实战之理财系统_第14张图片

定义MyErrorController类

package com.uos.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 errorViewResolvers) {
        super(errorAttributes, errorProperties, errorViewResolvers);
    }

    @Override
    protected Map getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        Map 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.uos.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> 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中添加相关属性

//获取错误种类
ErrorEnum errorEnum = ErrorEnum.getByCode(errorCode);
//添加相关属性
attrs.put("message",errorEnum.getMessage());
attrs.put("code",errorEnum.getCode());
attrs.put("canRetry",errorEnum.isCanRetry());

新建ErrorControllerAdvice类

 

package com.uos.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.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

/**
 * 统一错误处理
 */

@ControllerAdvice(basePackages = {"com.uos.manager.controller"})
public class ErrorControllerAdvice {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseEntity handleException(Exception e){
        Map 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.uos.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.uos.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 
     * @return
     */
    public static  T postJSON(RestTemplate restTemplate, String url, Object param, Class responseType) {
        HttpEntity 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 makePostJSONEntiry(Object param) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        HttpEntity formEntity = new HttpEntity(
                JsonUtil.toJson(param), headers);
        log.info("rest-post-json-请求参数:{}", formEntity.toString());
        return formEntity;
    }


    public static HttpEntity makePostTextEntiry(Map param) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        HttpEntity formEntity = new HttpEntity(
                makeGetParamContent(param), headers);
        log.info("rest-post-text-请求参数:{}", formEntity.toString());
        return formEntity;
    }


    /**
     * 生成Get请求内容
     *
     * @param param
     * @param excluedes
     * @return
     */
    public static String makeGetParamContent(Map param, String... excluedes) {
        StringBuilder content = new StringBuilder();
        List 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.uos.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 normals = new ArrayList<>();

    //异常产品数据
    private static List 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 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, "查询失败");
        });
    }

 

数据库结果

 

 

spring boot项目实战之理财系统_第15张图片

 

 spring boot项目实战之理财系统_第16张图片

 

 spring boot项目实战之理财系统_第17张图片

 

 spring boot项目实战之理财系统_第18张图片

 

 spring boot项目实战之理财系统_第19张图片

 

 spring boot项目实战之理财系统_第20张图片

 

 spring boot项目实战之理财系统_第21张图片

 spring boot项目实战之理财系统_第22张图片spring boot项目实战之理财系统_第23张图片

 

 

 

 1 package com.uos.swagger;
 2 
 3 import org.springframework.boot.context.properties.ConfigurationProperties;
 4 import org.springframework.stereotype.Component;
 5 
 6 /**
 7  * swagger配置信息
 8  */
 9 @Component
10 @ConfigurationProperties(prefix = "swagger")
11 public class SwaggerInfo {
12     private String groupName = "controller";
13     private String basePackage;
14     private String antPath;
15     private String title = "HTTP API";
16     private String description = "管理端接口";
17     private String license = "Apache License Version 2.0";
18 
19     public String getGroupName() {
20         return groupName;
21     }
22 
23     public void setGroupName(String groupName) {
24         this.groupName = groupName;
25     }
26 
27     public String getBasePackage() {
28         return basePackage;
29     }
30 
31     public void setBasePackage(String basePackage) {
32         this.basePackage = basePackage;
33     }
34 
35     public String getAntPath() {
36         return antPath;
37     }
38 
39     public void setAntPath(String antPath) {
40         this.antPath = antPath;
41     }
42 
43     public String getTitle() {
44         return title;
45     }
46 
47     public void setTitle(String title) {
48         this.title = title;
49     }
50 
51     public String getDescription() {
52         return description;
53     }
54 
55     public void setDescription(String description) {
56         this.description = description;
57     }
58 
59     public String getLicense() {
60         return license;
61     }
62 
63     public void setLicense(String license) {
64         this.license = license;
65     }
66 }
SwaggerInfo

spring boot项目实战之理财系统_第24张图片

 

 

 1 package com.uos.swagger;
 2 
 3 
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
 6 import org.springframework.context.annotation.Bean;
 7 import org.springframework.context.annotation.ComponentScan;
 8 import org.springframework.context.annotation.Configuration;
 9 import org.springframework.util.StringUtils;
10 import springfox.documentation.builders.ApiInfoBuilder;
11 import springfox.documentation.builders.PathSelectors;
12 import springfox.documentation.builders.RequestHandlerSelectors;
13 import springfox.documentation.service.ApiInfo;
14 import springfox.documentation.spi.DocumentationType;
15 import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
16 import springfox.documentation.spring.web.plugins.Docket;
17 import springfox.documentation.swagger2.annotations.EnableSwagger2;
18 
19 /**
20  * Swagger配置类
21  */
22 @Configuration
23 @ComponentScan(basePackages = "com.uos.swagger")
24 @EnableSwagger2
25 public class SwaggerConfiguration {
26     @Autowired
27     private SwaggerInfo swaggerInfo;
28     @Bean
29     public Docket createRestApi() {
30         Docket docket = new Docket(DocumentationType.SWAGGER_2)
31                 .groupName(swaggerInfo.getGroupName())
32                 .apiInfo(apiInfo());
33         ApiSelectorBuilder builder = docket.select();
34         if (!StringUtils.isEmpty(swaggerInfo.getBasePackage())){
35             builder = builder.apis(RequestHandlerSelectors.basePackage(swaggerInfo.getBasePackage()));
36         }
37         if (!StringUtils.isEmpty(swaggerInfo.getAntPath())){
38             builder = builder.paths(PathSelectors.ant(swaggerInfo.getAntPath()));
39         }
40         return builder.build();
41 
42     }
43     private ApiInfo apiInfo() {
44         return new ApiInfoBuilder()
45                 .title(swaggerInfo.getTitle())
46                 .description(swaggerInfo.getDescription())
47                 .termsOfServiceUrl("http://springfox.io")
48                 .contact("uos")
49                 .license(swaggerInfo.getLicense())
50                 .version("2.0")
51                 .build();
52     }
53 }
SwaggerConfiguration

spring boot项目实战之理财系统_第25张图片

 

 

 1 package com.uos.swagger;
 2 
 3 import org.springframework.context.annotation.Import;
 4 import springfox.documentation.swagger2.annotations.EnableSwagger2;
 5 
 6 import java.lang.annotation.Documented;
 7 import java.lang.annotation.Retention;
 8 import java.lang.annotation.Target;
 9 
10 /**
11  *开启swagger文档自动生成功能
12  */
13 @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
14 @Target(value = { java.lang.annotation.ElementType.TYPE })
15 @Documented
16 @Import(SwaggerConfiguration.class)
17 /*@EnableSwagger2*/
18 public @interface EnableMySwagger {
19 }
EnableMySwagger

spring boot项目实战之理财系统_第26张图片

 

 spring boot项目实战之理财系统_第27张图片

 

 

 

 

 spring boot项目实战之理财系统_第28张图片

spring boot项目实战之理财系统_第29张图片

 spring boot项目实战之理财系统_第30张图片

 

 spring boot项目实战之理财系统_第31张图片

 

 spring boot项目实战之理财系统_第32张图片

 

 spring boot项目实战之理财系统_第33张图片

 

 spring boot项目实战之理财系统_第34张图片

spring boot项目实战之理财系统_第35张图片

 

 spring boot项目实战之理财系统_第36张图片

 

 spring boot项目实战之理财系统_第37张图片

 

 spring boot项目实战之理财系统_第38张图片

 

 

package com.uos.api.domain;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.springframework.data.domain.Pageable;

import java.math.BigDecimal;
import java.util.List;

/**
 *产品相关rpc请求对象
 */
public class ProductRpcReq {
    private List idList;
    private BigDecimal minRewardRate;
    private BigDecimal maxRewardRate;
    private List statusList;
    private Pageable pageable;

    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }

    public List getIdList() {
        return idList;
    }

    public void setIdList(List idList) {
        this.idList = idList;
    }

    public BigDecimal getMinRewardRate() {
        return minRewardRate;
    }

    public void setMinRewardRate(BigDecimal minRewardRate) {
        this.minRewardRate = minRewardRate;
    }

    public BigDecimal getMaxRewardRate() {
        return maxRewardRate;
    }

    public void setMaxRewardRate(BigDecimal maxRewardRate) {
        this.maxRewardRate = maxRewardRate;
    }

    public List getStatusList() {
        return statusList;
    }

    public void setStatusList(List statusList) {
        this.statusList = statusList;
    }

    public Pageable getPageable() {
        return pageable;
    }

    public void setPageable(Pageable pageable) {
        this.pageable = pageable;
    }
}
ProductRpcReq

spring boot项目实战之理财系统_第39张图片

 

 spring boot项目实战之理财系统_第40张图片

 

 spring boot项目实战之理财系统_第41张图片

 

 

package com.uos.manager.rpc;

import com.googlecode.jsonrpc4j.JsonRpcService;
import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImpl;
import com.uos.api.ProductRpc;
import com.uos.api.domain.ProductRpcReq;
import com.uos.entity.Product;
import com.uos.manager.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

/**
 * rpc服务实现类
 */
@AutoJsonRpcServiceImpl
@Service
public class ProductRpcImpl implements ProductRpc {
    private static Logger LOG = LoggerFactory.getLogger(ProductRpcImpl.class);
    @Autowired
    private ProductService productService;
    @Override
    public Page query(ProductRpcReq req) {
        LOG.info("查询多个产品,请求{}",req);
        Page result = productService.query(req.getIdList(),req.getMinRewardRate(),req.getMaxRewardRate(),req.getStatusList(),req.getPageable());
        LOG.info("查询多个产品,结果{}",result);
        return result;
    }

    @Override
    public Product findOne(String id) {
        LOG.info("查询产品详情,请求{}",id);
        Product result = productService.findOne(id);
        LOG.info("查询产品详情,结果{}",result);
        return result;
    }
}
ProductRpc

spring boot项目实战之理财系统_第42张图片

 

 

 spring boot项目实战之理财系统_第43张图片

 

 spring boot项目实战之理财系统_第44张图片

package com.uos.seller.service;

import com.uos.api.ProductRpc;
import com.uos.api.domain.ProductRpcReq;
import com.uos.entity.Product;
import com.uos.entity.enums.ProductStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * 产品相关服务
 */
@Service
public class ProductRpcService {
    @Autowired
    private ProductRpc productRpc;

    private static Logger LOG = LoggerFactory.getLogger(ProductRpcService.class);
    //查询全部产品
    public List findAll(){
        ProductRpcReq req = new ProductRpcReq();
        List status = new ArrayList<>();
        status.add(ProductStatus.IN_SELL.name());
        Pageable pageable = new PageRequest(0,1000, Sort.Direction.DESC,"rewardRate");
        req.setStatusList(status);

        LOG.info("rpc查询全部产品,请求:{}",req);
        List result = productRpc.query(req);
        LOG.info("rpc查询全部产品,结果:{}",result);
        return result;
    }
    @PostConstruct
    public void test(){
        findAll();
    }

    //查询单个产品
    public Product findOne(String id){
        LOG.info("rpc查询单个产品,请求:{}",id);
        Product result = productRpc.findOne(id);
        LOG.info("rpc查询单个产品,结果:{}",result);
        return result;
    }
    @PostConstruct
    public void init(){
        findOne("001");
    }
}
ProductRpcService

 

 

 spring boot项目实战之理财系统_第45张图片

 

 spring boot项目实战之理财系统_第46张图片

 

 spring boot项目实战之理财系统_第47张图片

 spring boot项目实战之理财系统_第48张图片

 

 

 spring boot项目实战之理财系统_第49张图片spring boot项目实战之理财系统_第50张图片

 

 

 

 

 spring boot项目实战之理财系统_第51张图片

spring boot项目实战之理财系统_第52张图片

 

 spring boot项目实战之理财系统_第53张图片

 

 spring boot项目实战之理财系统_第54张图片

 

 spring boot项目实战之理财系统_第55张图片

 

 

package com.uos.util;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RSAUtil {
    static Logger LOG = LoggerFactory.getLogger(RSAUtil.class);

    private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";                     //签名算法
    private static final String KEY_ALGORITHM = "RSA";        //加密算法RSA

    /**
     * 公钥验签
     *
     * @param text      原字符串
     * @param sign      签名结果
     * @param publicKey 公钥
     * @return 验签结果
     */
    public static boolean verify(String text, String sign, String publicKey) {
        try {
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            PublicKey key = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.decodeBase64(publicKey)));
            signature.initVerify(key);
            signature.update(text.getBytes());
            return signature.verify(Base64.decodeBase64(sign));
        } catch (Exception e) {
            LOG.error("验签失败:text={},sign={}", text, sign, e);
        }
        return false;
    }

    /**
     * 签名字符串
     *
     * @param text       需要签名的字符串
     * @param privateKey 私钥(BASE64编码)
     * @return 签名结果(BASE64编码)
     */
    public static String sign(String text, String privateKey) {
        byte[] keyBytes = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(privateK);
            signature.update(text.getBytes());
            byte[] result = signature.sign();
            return Base64.encodeBase64String(result);
        } catch (Exception e) {
            LOG.error("签名失败,text={}", text, e);
        }
        return null;
    }

}
RSAUtil

spring boot项目实战之理财系统_第56张图片

(二)、下单功能的实现

1.订单管理

spring boot项目实战之理财系统_第57张图片

 

 

 

/**
 * 订单管理
 */
public interface OrderRepository extends JpaRepository, JpaSpecificationExecutor {
}

2.订单服务

/**
 * 订单服务
 */
@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private ProductRpcService productRpcService;

    //申购订单
    public Order apply(Order order){
        //数据校验
        checkOrder(order);
        //完善订单数据
        completeOrder(order);
        order = orderRepository.saveAndFlush(order);
        return order;
    }

    //完善订单数据
    private void completeOrder(Order order) {
        order.setOrderId(UUID.randomUUID().toString().replaceAll("-",""));
        order.setOrderType(OrderType.APPLY.name());
        order.setOrderStatus(OrderStatus.SUCCESS.name());
        order.setUpdateAt(new Date());
    }

    //校验数据
    private void checkOrder(Order order) {
        //必填字段
        Assert.notNull(order.getOuterOrderId(),"需要外部订单编号");
        Assert.notNull(order.getChanId(),"需要渠道编号");
        Assert.notNull(order.getChanUserId(),"需要用户编号");
        Assert.notNull(order.getProductId(),"需要产品编号");
        Assert.notNull(order.getAmount(),"需要购买金额");
        Assert.notNull(order.getCreateAt(),"需要购买时间");
        //产品是否存在及金额是否符合要求
        Product product = productRpcService.findOne(order.getProductId());
        Assert.notNull(product,"产品不存在");
        /*
        如果有起投金额,则购买金额需要大于投资金额
        如果有投资步长,则超过起投金额的部分需要为投资步长的整数倍
         */
        Assert.isTrue(order.getAmount().compareTo(product.getThresholdAmount()) > 0,"购买金额不正确");

    }
}

3.订单相关

/**
 *订单相关
 */
@RestController
@RequestMapping("/order")
public class OrderController {
    static Logger LOG = LoggerFactory.getLogger(OrderController.class);
    @Autowired
    private OrderService orderService;
    /*
    下单
     */
    @RequestMapping(value = "/apply",method = RequestMethod.POST)
    public Order apply(@RequestBody Order order){
        LOG.info("申购请求:{}",order);
        order = orderService.apply(order);
        LOG.info("申购结果:{}",order);
        return order;
    }

}

4.连接数据库相关配置

spring boot项目实战之理财系统_第58张图片

 

 

 spring boot项目实战之理财系统_第59张图片

 

 5.在SellerApp添加注解

@EntityScan("com.uos.entity")

 

 

 6.修改时间格式

spring boot项目实战之理财系统_第60张图片

 

 7.测试post请求

spring boot项目实战之理财系统_第61张图片

(三)、为下单添加RSA加签验签

1.添加SignText类

spring boot项目实战之理财系统_第62张图片

 

 2.添加OrderParam类

spring boot项目实战之理财系统_第63张图片

 

 

/**
 * 下单请求参数
 */
public class OrderParam implements SignText {
    //渠道id
    private String chanId;
    private String chanUserId;
    private String productId;
    private BigDecimal amount;
    private String outerOrderId;
    private String orderStatus;
    private String memo;
    @JsonFormat(pattern = "YY-MM-DD HH:mm:ss")
    private Date createAt;

    public String getChanId() {
        return chanId;
    }

    public void setChanId(String chanId) {
        this.chanId = chanId;
    }

    public String getChanUserId() {
        return chanUserId;
    }

    public void setChanUserId(String chanUserId) {
        this.chanUserId = chanUserId;
    }

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }

    public String getOuterOrderId() {
        return outerOrderId;
    }

    public void setOuterOrderId(String outerOrderId) {
        this.outerOrderId = outerOrderId;
    }

    public String getOrderStatus() {
        return orderStatus;
    }

    public void setOrderStatus(String orderStatus) {
        this.orderStatus = orderStatus;
    }

    public String getMemo() {
        return memo;
    }

    public void setMemo(String memo) {
        this.memo = memo;
    }

    public Date getCreateAt() {
        return createAt;
    }

    public void setCreateAt(Date createAt) {
        this.createAt = createAt;
    }
}

3.修改OrderController类

spring boot项目实战之理财系统_第64张图片

 

4.在service下添加SignService类

/**
 * 签名服务
 */
@Service
public class SignService {
    static Map PUBLIC_KEYS = new HashMap<>();
    static {
        PUBLIC_KEYS.put("1000","MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/91UoHgGjgPKpNg2oPZKHe4U1\n" +
                "Bv2pPA4h7A2WPtPWX6OhcIWXTtsZM57vlu0jIFXKYKH+aeu3Bp+d9X8a0cw50AMa\n" +
                "70DOT0yr+PE5u1zyWnqqU+va/5J+jSCBx7ur4msQYhkD5RlYMVQ//AUqI5ACArIT\n" +
                "B5U4FyS6gEyCjkT/ewIDAQAB");
    }
    /*
    根据授权编号来获取公钥
     */
    public String publicKey(String authId) {
        return PUBLIC_KEYS.get(authId);
    }
}

5.在sign下添加SignAop 类

/**
 * 验签Aop
 */
@Component
//切面
@Aspect
public class SignAop {
    @Autowired
    private SignService signService;
    @Before(value = "execution(* com.uos.seller.controller.*.*(..)) && args(authId,sign,text,..)")
    public void verify(String authId,String sign,SignText text){
        String publicKey = signService.publicKey(authId);
        Assert.isTrue(RSAUtil.verify(text.toText(),sign,publicKey),"验签失败");
    }
}

 (四)、对账介绍

spring boot项目实战之理财系统_第65张图片

 

 

 spring boot项目实战之理财系统_第66张图片

 

 对账流程

 

 

 

 

spring boot项目实战之理财系统_第67张图片

 

 生成对账文件

创建verification_order表

CREATE TABLE `verification_order` (
  `order_id` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '订单编号',
  `chan_id` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '渠道编号',
  `product_id` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '产品编号',
  `chan_user_id` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '渠道用户编号',
  `order_type` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '类型,APPLY:申购,REDEEM:赎回',
  `outer_order_id` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '外部订单编号',
  `amount` decimal(15,3) NOT NULL COMMENT '金额',
  `create_at` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

创建实体类

spring boot项目实战之理财系统_第68张图片

 

 

 

 创建VerifyRepository

spring boot项目实战之理财系统_第69张图片

 

 

/**
 * 对账相关
 */
public interface VerifyRepository extends JpaRepository, JpaSpecificationExecutor {
    /*
    查询某段时间[start,end)某个渠道chanId的对账数据
     */
    @Query(value = "select CONCAT_WS('|',order_id,outer_order_id,chan_id,chan_user_id,product_id,order_type,amount,DATE_FORMAT(create_at,'%Y-%m-%d %H:%i:%s'))\n" +
            "from order_t\n" +
            "where order_status = 'success' and chan_id = ?1 and create_at >= ?2 and create_at < ?3",nativeQuery = true)
    List queryVerificationOrders(String chanId, Date start,Date end);
}

在service下创建相应的服务类VerifyService

/**
 * 对账服务
 */
@Service
public class VerifyService {
    @Autowired
    private VerifyRepository verifyRepository;
    //生成文件的路径
    @Value("${verification.rootdir:/opt/verification}")
    private String rootDir;
    //定义换行符
    private static String END_LINE = System.getProperty("line.separator","\n");
    //定义时间
    private static DateFormat DAY_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    /*
    生成某个渠道某天的对账文件
     */
    public File makeVerificationFile(String chanId, Date day) {
        File path = getPath(chanId,day);
        if (path.exists()) {
            return path;
        }
        try {
            path.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 构造起止时间
        String start_str = DAY_FORMAT.format(day);
        Date start = null;
        try {
            start = DAY_FORMAT.parse(start_str);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        Date end = new Date(start.getTime() + 24 * 60 * 60 * 1000);

        List orders = verifyRepository.queryVerificationOrders(chanId,start,end);
        String content = String.join(END_LINE,orders);
        FileUtil.writeAsString(path,content);
        return path;
    }
    // 获取对账文件路径
    public File getPath(String chanId, Date day) {
        String name = DAY_FORMAT.format(day) + "-" + chanId + ".txt";
        File path = Paths.get(rootDir,name).toFile();
        return path;
    }
}

 创建测试类

spring boot项目实战之理财系统_第70张图片

 

 

/**
 *对账测试类
 */
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//按照方法的名称字典顺序进行排序
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class VerifyTest {
    @Autowired
    private VerifyService verifyService;
    @Test
    public void makeVerificationTest() {
        Date day = new GregorianCalendar(2018,11,30).getTime();
        File file = verifyService.makeVerificationFile("22",day);
        System.out.println(file.getAbsolutePath());
    }
}

效果测试

spring boot项目实战之理财系统_第71张图片

 解析对账文件

创建枚举类ChanEnum

spring boot项目实战之理财系统_第72张图片

 

 

 

package com.uos.seller.enums;

/**
 * 渠道配置信息
 */
public enum  ChanEnum {
    ABC("22","ABC","/opt/ABC");
    private String chanId;
    private String chanName;

    private String ftpPath,ftpUser,ftpPwd;
    private String rootDir;

    public String getChanId() {
        return chanId;
    }

    public String getChanName() {
        return chanName;
    }

    public String getFtpPath() {
        return ftpPath;
    }

    public String getFtpUser() {
        return ftpUser;
    }

    public String getFtpPwd() {
        return ftpPwd;
    }

    public String getRootDir() {
        return rootDir;
    }

    ChanEnum(String chanId, String chanName, String rootDir) {
        this.chanId = chanId;
        this.chanName = chanName;
        this.rootDir = rootDir;
    }
    /*
    根据渠道编号获取渠道的配置
     */
    public static ChanEnum getByChanId(String chanId) {
        for (ChanEnum chanEnum : ChanEnum.values()) {
            if (chanEnum.getChanId().equals(chanId)){
                return chanEnum;
            }
        }
        return null;
    }
}

在VerifyService添加

 

 /*
    按照顺序解析字符串创建对账订单
     order_id,outer_order_id,chan_id,chan_user_id,product_id,order_type,amount,create_at
     */
    public static VerificationOrder parseLine(String line) {
        VerificationOrder order = new VerificationOrder();
        String[] props = line.split("\\|");
         order.setOrderId(props[0]);
         order.setOuterOrderId(props[1]);
         order.setChanId(props[2]);
         order.setChanUserId(props[3]);
         order.setProductId(props[4]);
         order.setOrderType(props[5]);
         order.setAmount(new BigDecimal(props[6]));
        try {
            order.setCreateAt(DATETIME_FORMAT.parse(props[7]));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return order;
    }
    /*
    保存渠道订单数据
     */
    public void saveChanOrders(String chanId,Date day) {
        ChanEnum conf = ChanEnum.getByChanId(chanId);
        //根据配置从ftp下载对账的对账数据
        File file = getPath(conf.getRootDir(),chanId,day);
        if (!file.exists()){
            return;
        }
        String content = null;
        try {
            content = FileUtil.readAsString(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String[] lines = content.split(END_LINE);
        List orders = new ArrayList<>();
        for (String line : lines) {
            orders.add(parseLine(line));
        }
        verifyRepository.saveAll(orders);
    }

 

在测试类添加测试方法

spring boot项目实战之理财系统_第73张图片

 

 效果测试

spring boot项目实战之理财系统_第74张图片

 对账功能的实现

在VerifyRepository中添加

     // 长款
    @Query(value = "SELECT t.`order_id` FROM order_t t LEFT JOIN verification_order v ON t.`chan_id` = ?1 AND t.`outer_order_id` = v.`order_id` WHERE v.`order_id` IS NULL AND t.create_at >= ?2 AND t.create_at < ?3",nativeQuery = true)
    List queryExcessOrders(String chanId, Date start,Date end);
    // 漏单
    @Query(value = "SELECT v.`order_id` FROM verification_order v LEFT JOIN order_t t ON t.`chan_id` = ?1 AND v.`outer_order_id` = t.`order_id` WHERE t.`order_id` IS NULL AND v.create_at >= ?2 AND v.create_at < ?3",nativeQuery = true)
    List queryMissOrders(String chanId, Date start,Date end);
    //不一致
    @Query(value = "SELECT t.order_id FROM order_t t JOIN verification_order v ON t.`chan_id` = ?1 AND t.`outer_order_id` = v.`order_id` WHERE CONCAT_WS('|',t.chan_id,t.chan_user_id,t.product_id,t.order_type,t.amount,DATE_FORMAT( t.create_at,'%Y-%m-%d %H:%i:%s')) != CONCAT_WS('|',v.chan_id,v.chan_user_id,v.product_id,v.order_type,v.amount,DATE_FORMAT( v.create_at,'%Y-%m-%d %H:%i:%s')) AND t.create_at >= ?2 AND t.create_at < ?3",nativeQuery = true)
    List queryDifferentOrders(String chanId, Date start,Date end);

在VerifyService中添加方法

spring boot项目实战之理财系统_第75张图片

 

 

 public List verifyOrder(String chanId,Date day) {
        List errors = new ArrayList<>();
        Date start = getStartOfDay(day);
        Date end = add24Hours(start);
        List excessOrders = verifyRepository.queryExcessOrders(chanId,start,end);
        List missOrders = verifyRepository.queryMissOrders(chanId,start,end);
        List differentOrders = verifyRepository.queryDifferentOrders(chanId,start,end);
        errors.add("长款订单号:" + String.join(",",excessOrders));
        errors.add("漏单订单号:" + String.join(",",missOrders));
        errors.add("不一致订单号:" + String.join(",",differentOrders));
        return errors;
    }

在测试类中添加

spring boot项目实战之理财系统_第76张图片

 

 

 

@Test
    public void verifyTest() {
        Date day = new GregorianCalendar(2018,11,30).getTime();
        System.out.println(String.join(";", verifyService.verifyOrder("22", day)));
    }

效果测试

spring boot项目实战之理财系统_第77张图片

 定时对账功能的实现

spring boot项目实战之理财系统_第78张图片

 

 

 

/**
 * 定时对账任务
 */
@Component
public class VerifyTask {
    @Autowired
    private VerifyService verifyService;
    // 生成对账文件
    @Scheduled(cron = "0 0 1,3,5 * * ? ")
    public void makeVerificationFile() {
        Date yesterday = new Date(System.currentTimeMillis() -  24 * 60 * 60 * 1000);
        for (ChanEnum chanEnum : ChanEnum.values()) {
            verifyService.makeVerificationFile(chanEnum.getChanId(),yesterday);
        }
    }
    // 对账
    @Scheduled(cron = "0 0 2,4,6 * * ? ")
    public void verify() {
        Date yesterday = new Date(System.currentTimeMillis() -  24 * 60 * 60 * 1000);
        for (ChanEnum chanEnum : ChanEnum.values()) {
            verifyService.verifyOrder(chanEnum.getChanId(),yesterday);
        }
    }
    //测试
    @Scheduled(cron = "0/5 * * * * ? ")
    public void hello() {
        System.out.println("hello");
    }
}

spring boot项目实战之理财系统_第79张图片

 

JPA多数据源

主从复制,读写分离

spring boot项目实战之理财系统_第80张图片

 

 多数据源配置

创建DataAccessConfiguration配置类

/**
 *数据库相关操作配置
 */
@Configuration
public class DataAccessConfiguration {
    @Autowired
    private JpaProperties properties;
    // 配置主库
    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    // 配置备份库
    @Bean
    @ConfigurationProperties("spring.datasource.backup")
    public DataSource backupDataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
            EntityManagerFactoryBuilder builder,@Qualifier("primaryDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .properties(getVendorProperties(dataSource))
                .packages(Order.class)
                .persistenceUnit("primary")
                .build();
    }


    @Bean
    public LocalContainerEntityManagerFactoryBean backupEntityManagerFactory(
            EntityManagerFactoryBuilder builder,@Qualifier("backupDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .properties(getVendorProperties(dataSource))
                .packages(Order.class)
                .persistenceUnit("backup")
                .build();
    }

    protected MapgetVendorProperties(DataSource dataSource){
        Map vendorProperties =new LinkedHashMap<>();
        vendorProperties.putAll(properties.getHibernateProperties((HibernateSettings) dataSource));
        return vendorProperties;
    }


    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManagerFactory") LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager(primaryEntityManagerFactory.getObject());
        return transactionManager;
    }

    @Bean
    public PlatformTransactionManager backupTransactionManager(@Qualifier("backupEntityManagerFactory") LocalContainerEntityManagerFactoryBean backupEntityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager(backupEntityManagerFactory.getObject());
        return transactionManager;
    }

    // repository 扫描的时候,并不确定哪个先扫描,查看源代码
    @EnableJpaRepositories(basePackageClasses = OrderRepository.class,
            entityManagerFactoryRef = "primaryEntityManagerFactory",transactionManagerRef = "primaryTransactionManager")
    public class PrimaryConfiguration {
    }
    @EnableJpaRepositories(basePackageClasses = VerifyRepository.class,
            entityManagerFactoryRef = "backupEntityManagerFactory",transactionManagerRef = "backupTransactionManager")
    public class BackupConfiguration {
    }


}

 效果测试

spring boot项目实战之理财系统_第81张图片

 

 

 JPA读写分离

解决办法:

1.添加额外的接口,继承

spring boot项目实战之理财系统_第82张图片

 

 

 效果测试

spring boot项目实战之理财系统_第83张图片

 

 

 2.修改源代码

 

 

 spring boot项目实战之理财系统_第84张图片

 

 spring boot项目实战之理财系统_第85张图片

 

 spring boot项目实战之理财系统_第86张图片

 

效果测试

 spring boot项目实战之理财系统_第87张图片

 Tyk

spring boot项目实战之理财系统_第88张图片

 

 

 

swagger:
  basePackage: com.uos.seller.controller
  title: 销售端API
  description: >
    authId 是由本方提供给接口调用方用于安全控制及用户识别,请在需要此参数的接口上,通过请求头传递。
    sign 是接口调用方方便使用私钥对请求对象中的非空字段按字典排序之后的JSON字符串进行的签名。请在需要此参数的接口上,通过请求头传递

spring boot项目实战之理财系统_第89张图片

 

 

 运行架构

spring boot项目实战之理财系统_第90张图片

 

 

 升级管理端、销售端

spring boot项目实战之理财系统_第91张图片

 

 

 

 

重启管理端,修改对应的类

spring boot项目实战之理财系统_第92张图片

 

 

 

 

总结

spring boot项目实战之理财系统_第93张图片

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Spring,Boot)