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

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

一、项目准备

模块化开发的好处

  高内聚,低耦合

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

如何划分?

  业务层次

  功能划分

  重复使用

(一)、工程创建

构建工具:gradle

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

(2)系统环境变量配置

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

 

使用IDEA创建项目流程

 

 

创建模块

(二)、数据库设计

管理端                     

             产品表

销售端:

                      订单表

(三)、创建产品表

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;

  

(四)、创建订单表

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;

(五)、Entity模块

Product类

package com.uos.entity;import java.math.BigDecimal;
import java.util.Date;/*** 产品*/
@Entity
public class Product {@Idprivate 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;@Overridepublic 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 {@Idprivate String orderId;//渠道idprivate 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;@Overridepublic 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-8jpa:show-sql: true
server:context-path: /managerport: 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<Product,String>,JpaSpecificationExecutor<Product> {}

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);@Autowiredprivate 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);@Autowiredprivate 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<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>() {@Overridepublic 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;
}

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

(三)、统一错误处理

  用户友好的错误说明

  统一处理,简化业务代码

  异常标准化

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

 自定义错误页面

定义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<ErrorViewResolver> errorViewResolvers) {super(errorAttributes, errorProperties, errorViewResolvers);}@Overrideprotected 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.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 {@Beanpublic 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中添加相关属性

//获取错误种类
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)@ResponseBodypublic 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.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 <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.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<Product> normals = new ArrayList<>();//异常产品数据private static List<Product> exceptions = new ArrayList<>();@BeforeClasspublic 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() {@Overridepublic boolean hasError(ClientHttpResponse response) throws IOException {return false;}@Overridepublic void handleError(ClientHttpResponse response) throws IOException {}};rest.setErrorHandler(errorHandler);}@Testpublic void create(){normals.forEach(product -> {Product result = RestUtil.postJSON(rest,baseUrl+"/products",product,Product.class);Assert.notNull(result.getCreateAt(),"创建失败");});}@Testpublic 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()), "插入成功");});}}

  @Testpublic 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, "查询失败");});}

数据库结果

 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

 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

 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

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<String> idList;private BigDecimal minRewardRate;private BigDecimal maxRewardRate;private List<String> statusList;private Pageable pageable;@Overridepublic String toString() {return ReflectionToStringBuilder.toString(this);}public List<String> getIdList() {return idList;}public void setIdList(List<String> 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<String> getStatusList() {return statusList;}public void setStatusList(List<String> statusList) {this.statusList = statusList;}public Pageable getPageable() {return pageable;}public void setPageable(Pageable pageable) {this.pageable = pageable;}
}

ProductRpcReq

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);@Autowiredprivate ProductService productService;@Overridepublic Page<Product> query(ProductRpcReq req) {LOG.info("查询多个产品,请求{}",req);Page<Product> result = productService.query(req.getIdList(),req.getMinRewardRate(),req.getMaxRewardRate(),req.getStatusList(),req.getPageable());LOG.info("查询多个产品,结果{}",result);return result;}@Overridepublic Product findOne(String id) {LOG.info("查询产品详情,请求{}",id);Product result = productService.findOne(id);LOG.info("查询产品详情,结果{}",result);return result;}
}

ProductRpc

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 {@Autowiredprivate ProductRpc productRpc;private static Logger LOG = LoggerFactory.getLogger(ProductRpcService.class);//查询全部产品public List<Product> findAll(){ProductRpcReq req = new ProductRpcReq();List<String> 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<Product> result = productRpc.query(req);LOG.info("rpc查询全部产品,结果:{}",result);return result;}@PostConstructpublic void test(){findAll();}//查询单个产品public Product findOne(String id){LOG.info("rpc查询单个产品,请求:{}",id);Product result = productRpc.findOne(id);LOG.info("rpc查询单个产品,结果:{}",result);return result;}@PostConstructpublic void init(){findOne("001");}
}

ProductRpcService

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

(二)、下单功能的实现

1.订单管理

/*** 订单管理*/
public interface OrderRepository extends JpaRepository<Order, String>, JpaSpecificationExecutor<Order> {
}

2.订单服务

/*** 订单服务*/
@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate 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);@Autowiredprivate 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.连接数据库相关配置

 5.在SellerApp添加注解

@EntityScan("com.uos.entity")

 6.修改时间格式

 7.测试post请求

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

1.添加SignText类

 2.添加OrderParam类

/*** 下单请求参数*/
public class OrderParam implements SignText {//渠道idprivate 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类

4.在service下添加SignService类

/*** 签名服务*/
@Service
public class SignService {static Map<String,String > 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 {@Autowiredprivate 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),"验签失败");}
}

 (四)、对账介绍

 对账流程

 生成对账文件

创建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;

创建实体类

 创建VerifyRepository

/*** 对账相关*/
public interface VerifyRepository extends JpaRepository<VerificationOrder, String>, JpaSpecificationExecutor<VerificationOrder> {/*查询某段时间[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<String> queryVerificationOrders(String chanId, Date start,Date end);
}

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

/*** 对账服务*/
@Service
public class VerifyService {@Autowiredprivate 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<String> 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;}
}

 创建测试类

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

效果测试

解析对账文件

创建枚举类ChanEnum

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<VerificationOrder> orders = new ArrayList<>();for (String line : lines) {orders.add(parseLine(line));}verifyRepository.saveAll(orders);}

在测试类添加测试方法

 效果测试

 对账功能的实现

在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<String> 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<String> 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<String> queryDifferentOrders(String chanId, Date start,Date end);

在VerifyService中添加方法

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

在测试类中添加

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

效果测试

 定时对账功能的实现

/*** 定时对账任务*/
@Component
public class VerifyTask {@Autowiredprivate 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");}
}

JPA多数据源

主从复制,读写分离

 多数据源配置

创建DataAccessConfiguration配置类

/***数据库相关操作配置*/
@Configuration
public class DataAccessConfiguration {@Autowiredprivate 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@Primarypublic LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(EntityManagerFactoryBuilder builder,@Qualifier("primaryDataSource") DataSource dataSource) {return builder.dataSource(dataSource).properties(getVendorProperties(dataSource)).packages(Order.class).persistenceUnit("primary").build();}@Beanpublic LocalContainerEntityManagerFactoryBean backupEntityManagerFactory(EntityManagerFactoryBuilder builder,@Qualifier("backupDataSource") DataSource dataSource) {return builder.dataSource(dataSource).properties(getVendorProperties(dataSource)).packages(Order.class).persistenceUnit("backup").build();}protected Map<String, Object>getVendorProperties(DataSource dataSource){Map<String, Object> vendorProperties =new LinkedHashMap<>();vendorProperties.putAll(properties.getHibernateProperties((HibernateSettings) dataSource));return vendorProperties;}@Bean@Primarypublic PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManagerFactory") LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory) {JpaTransactionManager transactionManager = new JpaTransactionManager(primaryEntityManagerFactory.getObject());return transactionManager;}@Beanpublic 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 {}}

 效果测试

 JPA读写分离

解决办法:

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

 效果测试

 2.修改源代码

效果测试

Tyk

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

 运行架构

 升级管理端、销售端

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

总结

 

 

spring boot项目实战之理财系统相关推荐

  1. Vue + Spring Boot 项目实战(十五):动态加载后台菜单

    重要链接: 「系列文章目录」 「项目源码(GitHub)」 本篇目录 前言 一.后端实现 1.表设计 2.pojo 3.菜单查询接口(树结构查询) 二.前端实现 1.后台页面设计 2.数据处理 3.添 ...

  2. Vue + Spring Boot 项目实战(二十一):缓存的应用

    重要链接: 「系列文章目录」 「项目源码(GitHub)」 本篇目录 前言 一.缓存:工程思想的产物 二.Web 中的缓存 1.缓存的工作模式 2.缓存的常见问题 三.缓存应用实战 1.Redis 与 ...

  3. Vue + Spring Boot 项目实战(七):导航栏与图书页面设计

    本篇目录 前言 一.导航栏的实现 1.路由配置 2.使用 NavMenu 组件 二.图书管理页面 1.LibraryIndex.vue 2.SideMenu.vue 3.Books.vue 前言 之前 ...

  4. Vue + Spring Boot 项目实战(六):前端路由与登录拦截器

    本篇目录 前言 一.前端路由 二.使用 History 模式 三.后端登录拦截器 1.LoginController 2.LoginInterceptor 3.WebConfigurer 4.效果检验 ...

  5. STS创建Spring Boot项目实战(Rest接口、数据库、用户认证、分布式Token JWT、Redis操作、日志和统一异常处理)

    STS创建Spring Boot项目实战(Rest接口.数据库.用户认证.分布式Token JWT.Redis操作.日志和统一异常处理) 1.项目创建 1.新建工程 2.选择打包方式,这边可以选择为打 ...

  6. Vue + Spring Boot 项目实战(四):数据库的引入

    这一篇的主要内容是引入数据库并实现通过数据库验证用户名与密码. 本篇目录 一.引入数据库 1.安装数据库 2.使用 Navicat 创建数据库与表 二.使用数据库验证登录 1.项目相关配置 2.登录控 ...

  7. Vue + Spring Boot 项目实战(三):前后端结合测试(登录页面开发)

    前面我们已经完成了前端项目 DEMO 的构建,这一篇文章主要目的如下: 一.打通前后端之间的联系,为接下来的开发打下基础 二.登录页面的开发(无数据库情况下) 本篇目录 前言:关于开发环境 一.后端项 ...

  8. Vue + Spring Boot 项目实战(九):核心功能的前端实现

    本篇目录 前言 一.代码部分 1.EditForm.vue(新增) 2.SearchBar.vue(新增) 3.Books.vue(修改) 4.LibraryIndex.vue(修改) 5.SideM ...

  9. Vue + Spring Boot 项目实战(十七):后台角色、权限与菜单分配

    重要链接: 「系列文章目录」 「项目源码(GitHub)」 本篇目录 前言 一.角色.权限分配 1.用户信息表与行数据获取 2.角色分配 3.权限分配 二.菜单分配 下一步 前言 有感于公司旁边的兰州 ...

最新文章

  1. java交易系统_基于SSM框架的JAVA二手交易系统
  2. Python数据科学-技术详解与商业实践视频教程
  3. python middle()_Python自学笔记(七):函数
  4. [NOIP2017 TG D2T2]宝藏
  5. Bootstrap使用-1
  6. phpstorm 不能自动打开上次的历史文件
  7. 使用说明_预拌混凝土使用说明
  8. DB查询语句的编写和执行顺序
  9. 伪静态页面在iis7.0中的配置
  10. 二分查找与 bisect 模块
  11. java XML 通过BeanUtils的population为对象赋值 根据用户选择进行dom4j解析
  12. 计算机专业代码qian,专业分类号及学科代码对照表.doc
  13. gcforest 深度森林原理及实现
  14. RecyclerView.ViewHolder、Adapter
  15. Spring框架学习之路,完整版 持续更新,有代码仓库对比
  16. 云知声开源全栈语音交互方案
  17. IDEA加载文件:系统找不到指定文件
  18. 使用虚拟机备份软件恢复OpenStack虚拟机
  19. sizeof结构体指针
  20. WEB前端面试题整理

热门文章

  1. 论文梳理:3D ultrasound computer tomography: Hardware setup, reconstruction
  2. 如何用短信营销打动客户
  3. 一狼 GHOST 系统_sp3_v2.4.iso
  4. 小说网站和小说app用户体验
  5. 回调函数中删除对象引发的隐晦非法访问崩溃问题
  6. 基于JavaWeb的传染性疾病的了解与预防网站设计
  7. thinkphp使用try-catch
  8. 风云杯大学生信安大赛(Web部分题解)
  9. 轻松打造免开发智能电水壶
  10. 智慧物流之【无人值守过磅系统】方案