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 { @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<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); @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<Product> query(List<String> idList, BigDecimal minRewardRate, BigDecimal maxRewardRate, List<String> statusList, Pageable pageable){ LOG.debug("查询产品,idList={},minRewardRate={},maxRewardRate={},statusList={},pageable={}",idList,minRewardRate,maxRewardRate,statusList,pageable); Specification<Product> specification = new Specification<Product>() { @Override public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder cb) { //获取相关的列 Expression<String> idCol = root.get("id"); Expression<BigDecimal> rewardRateCol = root.get("rewardRate"); Expression<String> statusCol = root.get("status"); //定义断言列表,用来生成断言 List<Predicate> predicates = new ArrayList<>(); //判断产品编号的情况 if (idList != null && idList.size() > 0){ predicates.add(idCol.in(idList)); } if (BigDecimal.ZERO.compareTo(minRewardRate) < 0){ predicates.add(cb.ge(rewardRateCol,minRewardRate)); } if (BigDecimal.ZERO.compareTo(minRewardRate) < 0){ predicates.add(cb.le(rewardRateCol,maxRewardRate)); } //判断产品状态的情况 if (statusList != null && statusList.size() > 0){ predicates.add(statusCol.in(statusList)); } query.where(predicates.toArray(new Predicate[0])); return null; } }; Page<Product> page = repository.findAll(specification,pageable); LOG.debug("查询产品,结果={}",page); return page; }
在ProductController中添加
/** * 分页查询 */ @RequestMapping(value = "",method = RequestMethod.GET) public Page<Product> query(String ids, BigDecimal minRewardRate,BigDecimal maxRewardRate, String status,@RequestParam(defaultValue = "0") int pageNum,@RequestParam(defaultValue = "10") int pageSize){ LOG.info("查询产品,ids={},minRewardRate={},maxRewardRate={},status,pageNum={},pageSize={}"); List<String> idList = null,statusList = null; //判断产品编号 if (!StringUtils.isEmpty(ids)){ idList = Arrays.asList(ids.split(",")); } //判断产品状态 if (!StringUtils.isEmpty(status)){ statusList = Arrays.asList(status.split(",")); } Pageable pageable = new PageRequest(pageNum,pageSize); Page<Product> page = service.query(idList,minRewardRate,maxRewardRate,statusList,pageable); LOG.info("查询产品,结果={}",page); return page; }
在管理端启动类上添加@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); } @Override protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { Map<String, Object> attrs = super.getErrorAttributes(request, includeStackTrace); //去除无关的属性 attrs.remove("timestamp"); attrs.remove("status"); attrs.remove("error"); attrs.remove("exception"); attrs.remove("path"); return attrs; } }
定义ErrorConfiguration类 错误处理相关配置
package com.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<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) @ResponseBody public ResponseEntity handleException(Exception e){ Map<String, Object> attrs = new HashMap<>(); String errorCode = e.getMessage(); ErrorEnum errorEnum = ErrorEnum.getByCode(errorCode); //添加相关属性 attrs.put("message",errorEnum.getMessage()); attrs.put("code",errorEnum.getCode()); attrs.put("canRetry",errorEnum.isCanRetry()); attrs.put("type","advice"); return new ResponseEntity(attrs, HttpStatus.INTERNAL_SERVER_ERROR); } }
说明:ControllerAdvice是Controller的增强
(四)、自动化测试
采用功能测试,使用JUnit框架
在util下创建JsonUtil类
package com.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<>(); @BeforeClass public static void init(){ Product p1 = new Product("T001", "灵活宝1号", ProductStatus.AUDITING.name(), BigDecimal.valueOf(10), BigDecimal.valueOf(1), BigDecimal.valueOf(3.42)); Product p2 = new Product("T002", "活期盈-金色人生", ProductStatus.AUDITING.name(), BigDecimal.valueOf(10), BigDecimal.valueOf(0), BigDecimal.valueOf(3.28)); Product p3 = new Product("T003", "朝朝盈-聚财", ProductStatus.AUDITING.name(), BigDecimal.valueOf(100), BigDecimal.valueOf(10), BigDecimal.valueOf(3.86)); normals.add(p1); normals.add(p2); normals.add(p3); Product e1 = new Product(null, "编号不可为空", ProductStatus.AUDITING.name(), BigDecimal.valueOf(10), BigDecimal.valueOf(1), BigDecimal.valueOf(2.34)); Product e2 = new Product("E002", "收益率范围错误", ProductStatus.AUDITING.name(), BigDecimal.ZERO, BigDecimal.valueOf(1), BigDecimal.valueOf(31)); Product e3 = new Product("E003", "投资步长需为整数", ProductStatus.AUDITING.name(), BigDecimal.ZERO, BigDecimal.valueOf(1.01), BigDecimal.valueOf(3.44)); exceptions.add(e1); exceptions.add(e2); exceptions.add(e3); ResponseErrorHandler errorHandler = new ResponseErrorHandler() { @Override public boolean hasError(ClientHttpResponse response) throws IOException { return false; } @Override public void handleError(ClientHttpResponse response) throws IOException { } }; rest.setErrorHandler(errorHandler); } @Test public void create(){ normals.forEach(product -> { Product result = RestUtil.postJSON(rest,baseUrl+"/products",product,Product.class); Assert.notNull(result.getCreateAt(),"创建失败"); } ); } @Test public void createException(){ exceptions.forEach(product -> { Map<String,String> result = RestUtil.postJSON(rest,baseUrl+"/products",product, HashMap.class); Assert.isTrue(result.get("message").equals(product.getName()), "插入成功"); } ); }
}
@Test public void findOne() { normals.forEach(product -> { Product result = rest.getForObject(baseUrl + "/products/" + product.getId(), Product.class); Assert.isTrue(result.getId().equals(product.getId()), "查询失败"); }); exceptions.forEach(product -> { Product result = rest.getForObject(baseUrl + "/products/" + product.getId(), Product.class); Assert.isNull(result, "查询失败"); }); }
数据库结果

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 }

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 }

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 }

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; @Override public 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; } }

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<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; } @Override public Product findOne(String id) { LOG.info("查询产品详情,请求{}",id); Product result = productService.findOne(id); LOG.info("查询产品详情,结果{}",result); return result; } }

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<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; } @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"); } }

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; } }
(二)、下单功能的实现
1.订单管理
/** * 订单管理 */ public interface OrderRepository extends JpaRepository<Order, String>, JpaSpecificationExecutor<Order> { }
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.连接数据库相关配置
5.在SellerApp添加注解
@EntityScan("com.uos.entity")
6.修改时间格式
7.测试post请求
(三)、为下单添加RSA加签验签
1.添加SignText类
2.添加OrderParam类
/** * 下单请求参数 */ 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类
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 { @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),"验签失败"); } }
(四)、对账介绍
对账流程
生成对账文件
创建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 { @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<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 { @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()); } }
效果测试
解析对账文件
创建枚举类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; }
在测试类中添加
@Test public void verifyTest() { Date day = new GregorianCalendar(2018,11,30).getTime(); System.out.println(String.join(";", verifyService.verifyOrder("22", day))); }
效果测试
定时对账功能的实现
/** * 定时对账任务 */ @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"); } }
JPA多数据源
主从复制,读写分离
多数据源配置
创建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 Map<String, Object>getVendorProperties(DataSource dataSource){ Map<String, Object> 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 { } }
效果测试
JPA读写分离
解决办法:
1.添加额外的接口,继承
效果测试
2.修改源代码
效果测试
Tyk
swagger:
basePackage: com.uos.seller.controller
title: 销售端API
description: >
authId 是由本方提供给接口调用方用于安全控制及用户识别,请在需要此参数的接口上,通过请求头传递。
sign 是接口调用方方便使用私钥对请求对象中的非空字段按字典排序之后的JSON字符串进行的签名。请在需要此参数的接口上,通过请求头传递
运行架构
升级管理端、销售端
重启管理端,修改对应的类
总结