核心业务6:投资人投资实现
1.投资业务流程图
-----------------完善标的详情页展示-----------------
2.投资业务流程
3.展示标的页前端
4.展示标的页后端
5.标的详情页前端
6.标的详情页后端
-----------------完善标的详情页展示-----------------
-----------------投资人发起投资------------
7.投资人投资流程图
8.数据库表的详解
9.投资人投资流程详解
10.前端流程
11.汇付宝接口流程
12.尚融宝接口流程
-----------------投资人发起投资------------
核心业务6:投资人投资实现
1.投资业务流程图
①用户注册,登录,绑定,充值后
②可以选择投标
2.投资业务流程
①用户点击我要投资进入标的页面
②标的页面,点击标的可以进行投资
③点击具体标的可以展示标的的具体信息
④该标的详情页展示用户的余额,根据用户填写的投资金额动态算出获得利润
3.展示标的页前端
①调用接口
②具体页面代码
srb-site\pages\lend\index.vue
<template>
<!--列表-->
<div class="page-filter wrap">
<div class="breadcrumbs">
<a href="index.html">首页</a>><span class="cur">散标投资列表</span>
</div>
<div class="invest-filter" data-target="sideMenu">
<div class="filter-inner clearfix">
<div class="filter-item">
<div class="hd">
<h3>筛选投资项目</h3>
<label>
<input id="filterMulti" name="multiple_choice" type="checkbox" />
多选</label
>
</div>
<div class="bd">
<dl>
<dt>项目类型</dt>
<dd>
<ul>
<li class="n1">
<a
href="javascript:url('post_type','');"
id="post_type_"
class="active"
>不限</a
>
</li>
<li class="n2">
<a
href="javascript:url('post_type','car');"
id="post_type_car"
>车易贷</a
>
</li>
<li class="n3">
<a
href="javascript:url('post_type','house');"
id="post_type_house"
>房易贷</a
>
</li>
<li class="n4">
<a
href="javascript:url('post_type','bridge');"
id="post_type_bridge"
>赎楼贷</a
>
</li>
<li class="n5">
<a
href="javascript:url('post_type','worth');"
id="post_type_worth"
>债权贷</a
>
</li>
</ul>
</dd>
</dl>
<dl>
<dt>年利率</dt>
<dd>
<ul>
<li class="n1">
<a
href="javascript:url('borrow_interestrate','');"
id="borrow_interestrate_"
class="active"
>不限</a
>
</li>
<li class="n2">
<a
id="borrow_interestrate_1"
href="javascript:url('borrow_interestrate','1');"
>12%以下</a
>
</li>
<li class="n3">
<a
id="borrow_interestrate_2"
href="javascript:url('borrow_interestrate','2');"
>12%-14%</a
>
</li>
<li class="n4">
<a
id="borrow_interestrate_3"
href="javascript:url('borrow_interestrate','3');"
>14%-16%</a
>
</li>
<li class="n5">
<a
id="borrow_interestrate_4"
href="javascript:url('borrow_interestrate','4');"
>16%及以上</a
>
</li>
</ul>
</dd>
</dl>
<dl>
<dt>期限</dt>
<dd>
<ul>
<li class="n1">
<a
href="javascript:url('spread_month','');"
id="spread_month_"
class="active"
>不限</a
>
</li>
<li class="n2">
<a
id="spread_month_1"
href="javascript:url('spread_month','1');"
>1月以下</a
>
</li>
<li class="n3">
<a
id="spread_month_2"
href="javascript:url('spread_month','2');"
>1-3月</a
>
</li>
<li class="n4">
<a
id="spread_month_3"
href="javascript:url('spread_month','3');"
>3-6月</a
>
</li>
<li class="n5">
<a
id="spread_month_4"
href="javascript:url('spread_month','4');"
>6-12月</a
>
</li>
<li class="n6">
<a
id="spread_month_5"
href="javascript:url('spread_month','5');"
>12月及以上</a
>
</li>
</ul>
</dd>
</dl>
<dl class="repayment">
<dt>还款方式</dt>
<dd>
<ul>
<li class="n1">
<a
href="javascript:url('repay_style','');"
id="repay_style_"
class="active"
>不限</a
>
</li>
<li class="n2">
<a
id="repay_style_end"
href="javascript:url('repay_style','end');"
>到期还本付息</a
>
</li>
<li class="n2">
<a
id="repay_style_endmonth"
href="javascript:url('repay_style','endmonth');"
>按月付息,到期还本</a
>
</li>
<li class="n2">
<a
id="repay_style_month"
href="javascript:url('repay_style','month');"
>等额本息</a
>
</li>
</ul>
</dd>
</dl>
</div>
</div>
<div class="common-problem">
<h3>常见问题</h3>
<ul>
<li><a href="#">什么是债权贷?</a></li>
<li><a href="#">关于"债权贷"产品的说明</a></li>
<li><a href="#">金融理财收费标准</a></li>
<li><a href="#">债权贷和房易贷、车易贷有什么区别?</a></li>
</ul>
</div>
</div>
</div>
<div class="invest-list mrt30 clearfix">
<div class="hd">
<h3>投资列表</h3>
<div class="count">
<ul>
<li class="line">
散标投资交易金额 <span class="f20 bold"
>73.54亿元</span
>
</li>
<li>
累计赚取收益 <span class="f20 bold">2.52亿元</span>
</li>
</ul>
</div>
</div>
<div class="bd">
<div class="invest-table clearfix">
<div class="title clearfix">
<ul>
<li class="col-330">借款标题</li>
<li class="col-180">
<a href="javascript:url('order','account_up');" class=""
>借款金额</a
>
</li>
<li class="col-110">
<a href="javascript:url('order','apr_up');" class="">年利率</a>
</li>
<li class="col-150">
<a href="javascript:url('order','period_up');" class=""
>借款期限</a
>
</li>
<li class="col-150">还款方式</li>
<li class="col-120">
<a href="javascript:url('order','scale_up');" class=""
>借款进度</a
>
</li>
<li class="col-120-t">操作</li>
</ul>
</div>
<!------------投资列表-------------->
<div class="item" v-for="lend in lendList" :key="lend.id">
<ul>
<li class="col-330 col-t">
<NuxtLink :to="'/lend/' + lend.id" target="_blank">
<i class="icon icon-zhai"></i>
</NuxtLink>
<NuxtLink
class="f18"
:to="'/lend/' + lend.id"
:title="lend.title"
target="_blank"
>
{{ lend.title }}
</NuxtLink>
</li>
<li class="col-180">
<span class="f20 c-333"> {{ lend.amount }}元 </span>
</li>
<li class="col-110 relative">
<span class="f20 c-orange">
{{ lend.lendYearRate * 100 }}%
</span>
</li>
<li class="col-150">
<span class="f20 c-333">{{ lend.period }}个月</span>
</li>
<li class="col-150">{{ lend.param.returnMethod }}</li>
<li class="col-120">
<div class="circle">
<div class="left progress-bar">
<!-- <div
:class="
'progress-bgPic progress-bfb' +
Math.floor((lend.investAmount / lend.amount) * 10)
"
> -->
<div
:class="
'progress-bgPic progress-bfb' +
Math.floor((lend.investAmount / lend.amount) * 10)
"
>
<div class="show-bar">
{{ (lend.investAmount / lend.amount) * 100 }}%
</div>
</div>
</div>
</div>
</li>
<li class="col-120-2">
<NuxtLink
class="ui-btn btn-gray"
:to="'/lend/' + lend.id"
target="_blank"
>
{{ lend.param.status }}
</NuxtLink>
</li>
</ul>
</div>
<!------------投资列表-------------->
</div>
</div>
</div>
</div>
</template>
<script>
import '~/assets/css/index.css'
import '~/assets/css/detail.css'
export default {
async asyncData({ $axios }) {
let response = await $axios.$get('/api/core/lend/list')
return {
lendList: response.data.lendList,
}
},
}
</script>
4.展示标的页后端
①接口
②代码复用
- 之前获得的标的页详细信息可以拿来用
package com.atguigu.srb.core.controller.api;
import com.atguigu.common.result.R;
import com.atguigu.srb.core.pojo.entity.Lend;
import com.atguigu.srb.core.service.LendService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@Api(tags = "标的")
@RestController
@RequestMapping("/api/core/lend")
@Slf4j
public class LendController {
@Resource
private LendService lendService;
@ApiOperation("获取标的列表")
@GetMapping("/list")
public R list() {
List<Lend> lendList = lendService.selectList();
return R.ok().data("lendList", lendList);
}
}
5.标的详情页前端
①前端展示对象
- 标的信息
- 标的中借款人的信息
②前端接口
- 获取标的信息和借款人信息
- 查询账户余额
- 计算收益
③前端代码
srb-site\pages\lend_id.vue
<template>
<!--信息详细-->
<div class="item-detail wrap">
<div class="breadcrumbs">
<a href="/">首页</a>> <a href="/lend">散标投资列表</a>>
<span class="cur">项目详情</span>
</div>
<div class="item-detail-head clearfix" data-target="sideMenu">
<div class="hd">
<i class="icon icon-xin"></i>
<h2 style="width:70%">{{ lend.title }}</h2>
</div>
<!-- 标的信息开始 -->
<div class="bd clearfix">
<div class="data" style="width: auto;">
<ul>
<li>
<span class="f16">借款金额</span><br />
<span class="f30 c-333">{{ lend.amount }}</span>
元
</li>
<li class="relative">
<span class="f16">年利率</span><br />
<span class="f30 c-orange">{{ lend.lendYearRate * 100 }}% </span>
</li>
<li>
<span class="f16">借款期限</span><br />
<span class="f30 c-333">{{ lend.period }}</span>
个月
</li>
<li><span class="c-888">借款编号:</span>{{ lend.lendNo }}</li>
<li>
<span class="c-888">借款时间:</span>
{{ lend.lendStartDate }}至{{ lend.lendEndDate }}
</li>
<li>
<span class="c-888">还款方式:</span>{{ lend.param.returnMethod }}
</li>
<li class="colspan" style="line-height: 60px;">
<span class="c-888 fl">投标进度:</span>
<div class="progress-bar fl" style="margin-top:26px;">
<span
:style="
'width:' + (lend.investAmount / lend.amount) * 100 + '%'
"
></span>
</div>
<span class="c-orange">
{{ (lend.investAmount / lend.amount) * 100 }}%
</span>
<span>
已有{{ lend.investNum }}人投资{{ lend.investAmount }}元
</span>
</li>
</ul>
</div>
</div>
<!-- 标的信息开始 -->
<!-- 投资表单开始 -->
<div v-if="userType === 1 && lend.status === 1" class="bd clearfix">
<div class="data" style="width: auto;">
<el-form :inline="true" class="demo-form-inline">
<el-form-item label="投资金额">
<el-input
v-model="invest.investAmount"
:disabled="lend.status != 1"
@blur="getInterestCount()"
>
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="您将获得收益">
<span class="c-orange">{{ interestCount }}</span>
元
</el-form-item>
<el-form-item>
<el-checkbox v-model="agree">同意</el-checkbox>
<span class="orange">
<a href="#">《出借协议》</a>
</span>
<el-button
type="warning"
@click="commitInvest"
:disabled="!agree"
>
立即投资
</el-button>
</el-form-item>
</el-form>
<p>
您的账户余额 <span class="c-orange">{{ account }}</span> 元,
<a href="/user/recharge" class="c-888">马上充值</a>
</p>
</div>
</div>
<!-- 投资表单结束 -->
</div>
<!-- 投资记录 -->
<div class="item-detail-body clearfix mrt30 ui-tab">
<div class="ui-tab-nav hd">
<ul>
<li class="nav_li active">
<a href="javascript:;">投资记录</a>
</li>
</ul>
</div>
<div class="bd">
<div class="ui-tab-item active" style="display: block;">
<div class="repayment-list">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tbody>
<tr>
<th>投标人</th>
<th>投标金额</th>
<th>投标时间</th>
</tr>
</tbody>
<tbody id="repayment_content">
<tr v-for="lendItem in lendItemList" :key="lendItem.id">
<td>{{ lendItem.investName }}</td>
<td>
<span class="c-orange">¥{{ lendItem.investAmount }}</span
>account
</td>
<td>{{ lendItem.investTime }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 还款计划 -->
<div class="item-detail-body clearfix mrt30 ui-tab">
<div class="ui-tab-nav hd">
<ul>
<li class="nav_li active">
<a href="javascript:;">还款计划</a>
</li>
</ul>
</div>
<div class="bd">
<div class="ui-tab-item active" style="display: block;">
<div class="repayment-list">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<thead>
<tr>
<th>还款期数</th>
<th>还款日期</th>
<th>应还本金(元)</th>
<th>应还利息(元)</th>
<th>状态</th>
<th>是否逾期</th>
<th>操作</th>
</tr>
</thead>
<tbody id="repayment_content">
<tr v-for="lendReturn in lendReturnList" :key="lendReturn.id">
<td>{{ lendReturn.currentPeriod }}</td>
<td>{{ lendReturn.returnDate }}</td>
<td class="c-orange">¥{{ lendReturn.principal }}</td>
<td class="c-orange">¥{{ lendReturn.interest }}</td>
<td>{{ lendReturn.status === 0 ? '未还款' : '已还款' }}</td>
<td>
<span v-if="lendReturn.overdue">
是(逾期金额:{{ lendReturn.overdueTotal }}元)
</span>
<span v-else>否</span>
</td>
<td>
<a href="javascript:" @click="commitReturn(lendReturn.id)">
{{ lendReturn.status === 0 ? '还款' : '' }}
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 回款计划 -->
<div v-if="userType === 1" class="item-detail-body clearfix mrt30 ui-tab">
<div class="ui-tab-nav hd">
<ul>
<li class="nav_li active">
<a href="javascript:;">回款计划</a>
</li>
</ul>
</div>
<div class="bd">
<div class="ui-tab-item active" style="display: block;">
<div class="repayment-list">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<thead>
<tr>
<th>期数</th>
<th>本金(元)</th>
<th>利息(元)</th>
<th>本息(元)</th>
<th>计划回款日期</th>
<th>实际回款日期</th>
<th>状态</th>
<th>是否逾期</th>
</tr>
</thead>
<tbody id="repayment_content">
<tr
v-for="lendItemReturn in lendItemReturnList"
:key="lendItemReturn.id"
>
<td>{{ lendItemReturn.currentPeriod }}</td>
<td class="c-orange">¥{{ lendItemReturn.principal }}</td>
<td class="c-orange">¥{{ lendItemReturn.interest }}</td>
<td class="c-orange">¥{{ lendItemReturn.total }}</td>
<td>{{ lendItemReturn.returnDate }}</td>
<td>{{ lendItemReturn.realReturnTime }}</td>
<td>
{{ lendItemReturn.status === 0 ? '未还款' : '已还款' }}
</td>
<td>
<span v-if="lendItemReturn.overdue">
是(逾期金额:{{ lendReturn.overdueTotal }}元)
</span>
<span v-else>否</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 借款和借款人信息 -->
<div class="item-detail-body clearfix mrt30 ui-tab">
<div class="ui-tab-nav hd">
<ul>
<li class="nav_li active">
<a href="javascript:;">借款信息</a>
</li>
</ul>
</div>
<div class="bd">
<div class="ui-tab-item active" style="display: block;">
<div class="borrow-info" style="width:auto;">
<dl class="item">
<dt>
<h3>项目介绍</h3>
</dt>
<dd>
<div class="text">
<p class="MsoNormal" style="margin-left:0cm;text-indent:0cm;">
{{ lend.lendInfo }}
</p>
</div>
</dd>
</dl>
<dl class="item">
<dt>
<h3>借款人信息</h3>
</dt>
<dd>
<div class="text">
<p class="MsoNormal" style="margin-left:0cm;text-indent:0cm;">
姓名:{{ borrower.name }} <br />
手机号码:{{ borrower.mobile }} <br />
身份认证:已认证 <br />
注册时间:{{ borrower.createTime }} <br />
</p>
</div>
</dd>
</dl>
<dl class="item">
<dt>
<h3>审核信息</h3>
</dt>
<dd>
<div class="verify clearfix" style="width: auto;">
<ul>
<li>
<i class="icon icon-4"></i><br />
身份证
</li>
<li>
<i class="icon icon-5"></i><br />
户口本
</li>
<li>
<i class="icon icon-6"></i><br />
结婚证
</li>
<li>
<i class="icon icon-7"></i><br />
工作证明
</li>
<li>
<i class="icon icon-8"></i><br />
工资卡流水
</li>
<li>
<i class="icon icon-9"></i><br />
收入证明
</li>
<li>
<i class="icon icon-10"></i><br />
征信报告
</li>
<li>
<i class="icon icon-11"></i><br />
亲属调查
</li>
<li>
<i class="icon icon-19"></i><br />
行驶证
</li>
<li>
<i class="icon icon-20"></i><br />
车辆登记证
</li>
<li>
<i class="icon icon-21"></i><br />
车辆登记发票
</li>
<li>
<i class="icon icon-22"></i><br />
车辆交强险
</li>
<li>
<i class="icon icon-23"></i><br />
车辆商业保险
</li>
<li>
<i class="icon icon-24"></i><br />
车辆评估认证
</li>
</ul>
</div>
</dd>
</dl>
<dl class="item">
<dt>
<h3>风控步骤</h3>
</dt>
<dd>
<div class="text">
<p class="MsoNormal" style="margin-left:0cm;text-indent:0cm;">
调查:风控部对借款人各项信息进行了全面的电话征信,一切资料真实可靠。<span
></span>
</p>
<p class="MsoNormal" style="margin-left:0cm;text-indent:0cm;">
抵押物:全款长安福特福克斯汽车,车牌号:川<span>AYY***</span>,新车购买于<span>2013</span>年,裸车价<span>14</span>万,评估价<span>5</span>万。
</p>
<p class="MsoNormal" style="margin-left:0cm;text-indent:0cm;">
权证:汽车已入库、已办理相关手续等。
</p>
<p class="MsoNormal" style="margin-left:0cm;text-indent:0cm;">
担保:质押物担保。
</p>
<p class="MsoNormal" style="margin-left:0cm;text-indent:0cm;">
结论:此客户为老客户,上笔贷款<span>4</span>万元,标的号为<span>20200745682</span>,已结清,现因资金周转,再次申请贷款。借款人居住稳定,收入来源可靠,经风控综合评估,同意放款<span>4</span>万。
</p>
<p class="MsoNormal" style="margin-left:0cm;text-indent:0cm;">
保障:借款逾期<span>48</span>小时内,易贷风险准备金先行垫付。
</p>
</div>
<div class="step clearfix">
<ul>
<li><i class="icon icon-1"></i>资料审核</li>
<li><i class="icon icon-2"></i>实地调查</li>
<li><i class="icon icon-3"></i>资产评估</li>
<li class="no"><i class="icon icon-4"></i>发布借款</li>
</ul>
</div>
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import '~/assets/css/index.css'
import '~/assets/css/detail.css'
import cookie from 'js-cookie'
export default {
async asyncData({ $axios, params }) {
let lendId = params.id
let response = await $axios.$get('/api/core/lend/show/' + lendId)
return {
lend: response.data.lendDetail.lend, //标的详情
borrower: response.data.lendDetail.borrower, //借款人信息
}
},
data() {
return {
account: 0, //账户余额
agree: false, //是否同意协议
invest: {
lendId: 0, //标的id
investAmount: 100, //投资金额
},
interestCount: 0, //将获得收益
userType: 0, //用户类型
}
},
//此时方法在客户端的浏览器中执行,可以获取到cookie
mounted() {
//查询账户余额
this.fetchAccount()
//判断登录人的用户类型
this.fetchUserType()
},
methods: {
//查询账户余额
fetchAccount() {
let userInfo = cookie.get('userInfo')
if (userInfo) {
this.$axios
.$get('/api/core/userAccount/auth/getAccount')
.then((response) => {
this.account = response.data.account
console.log(this.account)
})
}
},
//获取登录人的用户类型
fetchUserType() {
let userInfo = cookie.get('userInfo')
if (userInfo) {
userInfo = JSON.parse(userInfo)
this.userType = userInfo.userType
}
},
//计算收益
getInterestCount() {
this.$axios
.$get(
`/api/core/lend/getInterestCount/${this.invest.investAmount}/${this.lend.lendYearRate}/${this.lend.period}/${this.lend.returnMethod}`
)
.then((response) => {
this.interestCount = response.data.interestCount
})
},
//投资
commitInvest() {},
},
}
</script>
⑤前端代码逻辑
- 进入详情页后,利用前端服务器渲染的技术先将标的和借款人信息渲染
- 查询账户余额显示出来
- 然后根据用户类型来确定是否展示立即投资条(判断类型)
6.标的详情页后端
①接口
②引入工具类
- 计算四种借款方式的受益
③查询账户余额
- controller
com.atguigu.srb.core.controller.api.UserAccountController
@ApiOperation("查询账户余额")
@GetMapping("/auth/getAccount")
public R getAccount(HttpServletRequest request){
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
BigDecimal account = userAccountService.getAccount(userId);
return R.ok().data("account", account);
}
- service
com/atguigu/srb/core/service/UserAccountService.java
BigDecimal getAccount(Long userId);
/**
* @param userId:
* @return BigDecimal
* @author Likejin
* @description 查询账户余额
* @date 2023/4/17 15:43
*/
@Override
public BigDecimal getAccount(Long userId) {
QueryWrapper<UserAccount> userAccountQueryWrapper = new QueryWrapper<>();
userAccountQueryWrapper.eq("user_id",userId);
UserAccount userAccount = baseMapper.selectOne(userAccountQueryWrapper);
BigDecimal amount = userAccount.getAmount();
return amount;
}
④获取标的信息
- controller:利用map封装数据,前面写过
com/atguigu/srb/core/controller/api/LendController.java
@ApiOperation("获取标的信息")
@GetMapping("/show/{id}")
public R show(
@ApiParam(value = "标的id", required = true)
@PathVariable Long id) {
Map<String, Object> lendDetail = lendService.getLendDetail(id);
return R.ok().data("lendDetail", lendDetail);
}
⑤计算投资受益
- controller
com/atguigu/srb/core/controller/api/LendController.java
@ApiOperation("计算投资收益")
@GetMapping("/getInterestCount/{invest}/{yearRate}/{totalmonth}/{returnMethod}")
public R getInterestCount(
@ApiParam(value = "投资金额", required = true)
@PathVariable("invest") BigDecimal invest,
@ApiParam(value = "年化收益", required = true)
@PathVariable("yearRate")BigDecimal yearRate,
@ApiParam(value = "期数", required = true)
@PathVariable("totalmonth")Integer totalmonth,
@ApiParam(value = "还款方式", required = true)
@PathVariable("returnMethod")Integer returnMethod) {
BigDecimal interestCount = lendService.getInterestCount(invest, yearRate, totalmonth, returnMethod);
return R.ok().data("interestCount", interestCount);
}
- service
com/atguigu/srb/core/service/LendService.java
BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod);
/**
* @param invest:
* @param yearRate:
* @param totalmonth:
* @param returnMethod:
* @return BigDecimal
* @author Likejin
* @description 计算利息
* @date 2023/4/17 16:31
*/
@Override
public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod) {
BigDecimal interestCount;
//计算总利息
if (returnMethod.intValue() == ReturnMethodEnum.ONE.getMethod()) {
interestCount = Amount1Helper.getInterestCount(invest, yearRate, totalmonth);
} else if (returnMethod.intValue() == ReturnMethodEnum.TWO.getMethod()) {
interestCount = Amount2Helper.getInterestCount(invest, yearRate, totalmonth);
} else if(returnMethod.intValue() == ReturnMethodEnum.THREE.getMethod()) {
interestCount = Amount3Helper.getInterestCount(invest, yearRate, totalmonth);
} else {
interestCount = Amount4Helper.getInterestCount(invest, yearRate, totalmonth);
}
return interestCount;
}
⑥代码逻辑
- 查询账户余额
直接利用token解析出user_id查询余额即可 - 获取标的详细信息
利用传入的参数标的id,组装一个标的数据lend和borrow的详细信息。
borrow的详细信息通过调用前面的写过封装的方法 - 计算投资受益
根据传入的投资金额,还款方式,期数,年化收益得到利息返回。
判断还款方式来计算不同的方式的利息返回(调用工具类)
7.投资人投资流程
①投资人点击立即投资
②根据尚融宝返回的表单自动提交数据到汇付宝,汇付宝返回表单回显页
③提交的汇付宝更新数据
④汇付宝同步返回页面
⑤汇付宝异步调用尚融宝接口更新尚融宝
8.数据库表的详解
①尚融宝数据库
- 标的lend表
- 相关标的借款信息 lend_item表
- 流水表trans_flow
②尚融宝三表关联
- trans_flow根据user_id与唯一账户绑定(三表都有user_id)
- lend_item和lend表通过lend的id进行绑定
③汇付宝表
- user_account
- user_invest
④汇付宝两表关联
- user_account和user_invest表通过user_code关联
⑤汇付宝和尚融宝表关联
- 尚融宝中的lend_item的lend_item_no与汇付宝中的user_invest的agent_bill_no关联,
- 汇付宝中的user_invest的agent_bill_no与尚融宝中的trans_flow的trans_no关联。
- 故三表关联
9.投资人投资流程详解
①投资人点击投资
- 尚融宝接收到投资金额数据
返回汇付宝需要提交的表单数据(自动提交)
更新自己的lend_item表,插入一条item(生成lend_item_no)。 - 尚融宝根据汇付宝需要数据封装表单数据
- 汇付宝返回给用户表单界面
②投资人在汇付宝界面中点击确定
- 汇付宝根据表单的vote_bind_code(更新user_account)和agent_bill_no以及vote_amt(更新user_invest)来更新自己的表单。
- 汇付宝同步返回页面
- 汇付宝异步向尚融宝发起更新数据请求
③汇付宝异步调用尚融宝
- 汇付宝封装数据给尚融宝
- 尚融宝利用vote_bind_code和vote_amt来更新user_account表(余额)
- 尚融宝更改lend_item的状态为已支付(状态)
- 尚融宝根据vote_amt来修改更新lend(已投资人数,已投资金额)
- 尚融宝根据agent_billno来创建一条交易流水trans_flow
10.前端流程
①按钮绑定事件
②代码
srb-site\pages\lend_id.vue
//投资
commitInvest() {
//校验用户是否登录
let userInfo = cookie.get('userInfo')
// console.log(typeof userInfo)
// console.log(!userInfo) //true
if (!userInfo) {
window.location.href = '/login'
return
}
//借款人看不到页面(双重校验)
//校验当前用户是否是投资人
let userInfoObj = JSON.parse(userInfo)
if (userInfoObj.userType == 2) {
//借款人
this.$message.error('借款人无法投资')
return
}
//判断标的是否超卖:标的已投金额 + 本次投资金额 > 标的总金额
if (
this.lend.investAmount + Number(this.invest.investAmount) >
this.lend.amount
) {
this.$message.error('标的可投资金额不足')
return
}
//是否是100的整数倍
// console.log(this.invest.investAmount)
// console.log(Number(this.invest.investAmount))
// console.log(typeof Number(this.invest.investAmount))
// return
if (
Number(this.invest.investAmount) === 0 ||
this.invest.investAmount % this.lend.lowestAmount != 0
) {
this.$message.error(`投资金额必须是${this.lend.lowestAmount}的整数倍`)
return
}
//余额的判断
if (this.invest.investAmount > this.account) {
this.$message.error('余额不足,请充值')
return
}
//数据提交
this.$alert(
'<div style="size: 18px;color: red;">您即将前往汇付宝确认标的</div>',
'前往汇付宝资金托管平台',
{
dangerouslyUseHTMLString: true,
confirmButtonText: '立即前往',
callback: (action) => {
console.log('action', action)
if (action === 'confirm') {
this.invest.lendId = this.lend.id
this.$axios
.$post('/api/core/lendItem/auth/commitInvest', this.invest)
.then((response) => {
// console.log(response.data.formStr)
// debugger
document.write(response.data.formStr)
})
}
},
}
)
},
},
11.汇付宝接口流程
①汇付宝接受表单数据
②汇付宝异步调用尚融宝请求数据
12.尚融宝接口流程
①尚融宝数据对象
- 封装前端数据(不是直接对应,还需修改)
②尚融宝接口
③尚融宝具体代码
- controller
package com.atguigu.srb.core.controller.api;
import com.alibaba.fastjson.JSON;
import com.atguigu.common.result.R;
import com.atguigu.srb.base.util.JwtUtils;
import com.atguigu.srb.core.hfb.RequestHelper;
import com.atguigu.srb.core.pojo.vo.InvestVO;
import com.atguigu.srb.core.service.LendItemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Api(tags = "标的的投资")
@RestController
@RequestMapping("/api/core/lendItem")
@Slf4j
public class LendItemController {
@Resource
LendItemService lendItemService;
@ApiOperation("会员投资提交数据")
@PostMapping("/auth/commitInvest")
//注意:此处的vo并不能完全封装前端对象,只能封装id和投资金额,其他的优token获得
public R commitInvest(@RequestBody InvestVO investVO, HttpServletRequest request) {
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
String userName = JwtUtils.getUserName(token);
investVO.setInvestUserId(userId);
investVO.setInvestName(userName);
//构建充值自动提交表单
String formStr = lendItemService.commitInvest(investVO);
return R.ok().data("formStr", formStr);
}
@ApiOperation("会员投资异步回调")
@PostMapping("/notify")
public String notify(HttpServletRequest request) {
Map<String, Object> paramMap = RequestHelper.switchMap(request.getParameterMap());
log.info("用户投资异步回调:" + JSON.toJSONString(paramMap));
//校验签名 P2pInvestNotifyVo
if(RequestHelper.isSignEquals(paramMap)) {
if("0001".equals(paramMap.get("resultCode"))) {
lendItemService.notify(paramMap);
} else {
log.info("用户投资异步回调失败:" + JSON.toJSONString(paramMap));
return "fail";
}
} else {
log.info("用户投资异步回调签名错误:" + JSON.toJSONString(paramMap));
return "fail";
}
return "success";
}
}
- service
package com.atguigu.srb.core.service;
import com.atguigu.srb.core.pojo.entity.LendItem;
import com.atguigu.srb.core.pojo.vo.InvestVO;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Map;
/**
* <p>
* 标的出借记录表 服务类
* </p>
*
* @author Likejin
* @since 2023-04-09
*/
public interface LendItemService extends IService<LendItem> {
String commitInvest(InvestVO investVO);
void notify(Map<String, Object> paramMap);
}
package com.atguigu.srb.core.service.impl;
import com.atguigu.common.exception.Assert;
import com.atguigu.common.result.ResponseEnum;
import com.atguigu.srb.core.enums.LendStatusEnum;
import com.atguigu.srb.core.enums.TransTypeEnum;
import com.atguigu.srb.core.hfb.FormHelper;
import com.atguigu.srb.core.hfb.HfbConst;
import com.atguigu.srb.core.hfb.RequestHelper;
import com.atguigu.srb.core.mapper.LendItemMapper;
import com.atguigu.srb.core.mapper.LendMapper;
import com.atguigu.srb.core.mapper.UserAccountMapper;
import com.atguigu.srb.core.pojo.bo.TransFlowBO;
import com.atguigu.srb.core.pojo.entity.Lend;
import com.atguigu.srb.core.pojo.entity.LendItem;
import com.atguigu.srb.core.pojo.vo.InvestVO;
import com.atguigu.srb.core.service.*;
import com.atguigu.srb.core.util.LendNoUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 标的出借记录表 服务实现类
* </p>
*
* @author Likejin
* @since 2023-04-09
*/
@Service
public class LendItemServiceImpl extends ServiceImpl<LendItemMapper, LendItem> implements LendItemService {
@Resource
private UserBindService userBindService;
@Resource
private LendMapper lendMapper;
@Resource
private UserAccountService userAccountService;
@Resource
private LendService lendService;
@Resource
private TransFlowService transFlowService;
@Resource
private UserAccountMapper userAccountMapper;
/**
* @param investVO:
* @return String
* @author Likejin
* @description 封装投资表单(尚融宝需要的)
* @date 2023/4/18 9:35
*/
@Override
public String commitInvest(InvestVO investVO) {
//健壮性校验
Long lendId = investVO.getLendId();
Lend lend = lendMapper.selectById(lendId);
//判断标的的状态为募资中
Assert.isTrue(lend.getStatus()== LendStatusEnum.INVEST_RUN.getStatus().intValue(), ResponseEnum.LEND_INVEST_ERROR);
//超卖:已投金额+当前投资金额 <= 标的金额 正常
BigDecimal sumAmount = lend.getInvestAmount().add(new BigDecimal(investVO.getInvestAmount()));
Assert.isTrue(sumAmount.doubleValue()<lend.getAmount().doubleValue(),ResponseEnum.LEND_FULL_SCALE_ERROR);
//判断用户余额:当前余额 >=当前投资金额
Long investUserId = investVO.getInvestUserId();
BigDecimal account = userAccountService.getAccount(investUserId);
Assert.isTrue(account.doubleValue()>=Double.parseDouble(investVO.getInvestAmount())
,ResponseEnum.NOT_SUFFICIENT_FUNDS_ERROR);
//获取paramMap中需要的参数
//获取投资人的绑定协议号
String bindCode = userBindService.getBindCodeByUserId(investUserId);
//获取借款人的绑定协议号
String benefitBindCode = userBindService.getBindCodeByUserId(lend.getUserId());
//产生投资记录,产生投资的订单编号lend_item(生成标的下的投资信息)
LendItem lendItem = new LendItem();
lendItem.setInvestUserId(investUserId);//投资人id
lendItem.setInvestName(investVO.getInvestName());//投资人名字
String lendItemNo = LendNoUtils.getLendItemNo();
lendItem.setLendItemNo(lendItemNo); //投资条目编号(一个Lend对应一个或多个LendItem)
lendItem.setLendId(investVO.getLendId());//对应的标的id
lendItem.setInvestAmount(new BigDecimal(investVO.getInvestAmount())); //此笔投资金额
lendItem.setLendYearRate(lend.getLendYearRate());//年化
lendItem.setInvestTime(LocalDateTime.now()); //投资时间
lendItem.setLendStartDate(lend.getLendStartDate()); //开始时间
lendItem.setLendEndDate(lend.getLendEndDate()); //结束时间
//计算平台预期受益(放款)
BigDecimal ExpectAmount = lendService.getInterestCount(
lendItem.getInvestAmount(),
lendItem.getLendYearRate(),
lend.getPeriod(),
lend.getReturnMethod());
lendItem.setExpectAmount(ExpectAmount);//投资人预期受益
//实际受益(未放款)
lendItem.setRealAmount(new BigDecimal(0));//投资人实际受益
//造成预期受益和实际受益不同:如果不满标则实际收益不同
//投资记录状态
lendItem.setStatus(0);//刚刚创建投资记录,账户信息尚未修改
baseMapper.insert(lendItem);//存入数据库
//组装投资相关的参数,提交到汇付宝资金托管平台==========================================
//在托管平台同步用户的投资信息,修改用户的账户资金信息==========================================
//封装提交至汇付宝的参数
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("agentId", HfbConst.AGENT_ID);
paramMap.put("voteBindCode", bindCode);
paramMap.put("benefitBindCode",benefitBindCode);
paramMap.put("agentProjectCode", lend.getLendNo());//项目标号
paramMap.put("agentProjectName", lend.getTitle());
//在资金托管平台上的投资订单的唯一编号,要和lendItemNo保持一致。
paramMap.put("agentBillNo", lendItemNo);//订单编号
paramMap.put("voteAmt", investVO.getInvestAmount());
paramMap.put("votePrizeAmt", "0");
paramMap.put("voteFeeAmt", "0");
paramMap.put("projectAmt", lend.getAmount()); //标的总金额
paramMap.put("note", "");
paramMap.put("notifyUrl", HfbConst.INVEST_NOTIFY_URL); //检查常量是否正确
paramMap.put("returnUrl", HfbConst.INVEST_RETURN_URL);
paramMap.put("timestamp", RequestHelper.getTimestamp());
String sign = RequestHelper.getSign(paramMap);
paramMap.put("sign", sign);
//构建充值自动提交表单
String formStr = FormHelper.buildForm(HfbConst.INVEST_URL, paramMap);
return formStr;
}
/**
* @param paramMap:
* @return void
* @author Likejin
* @description 投资人投资后汇付宝回调的接口
* @date 2023/4/18 10:14
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void notify(Map<String, Object> paramMap) {
//判断幂等性(判断交易流水的增加)
//获取投资编号
String agentBillNo = (String)paramMap.get("agentBillNo");
boolean result = transFlowService.isSaveTransFlow(agentBillNo);
if(result){
log.warn("幂等性返回");
return;
}
//修改账户金额:余额减去投资金额,冻结增加投资金额user_account
String voteBindCode = (String)paramMap.get("voteBindCode");
String voteAmt = (String)paramMap.get("voteAmt");
//余额中减少金额,冻结中增加金额
userAccountMapper.updateAccount(
voteBindCode,
new BigDecimal( "-"+voteAmt),
new BigDecimal(voteAmt)
);
//修改投资记录的状态lend_item
LendItem lendItem = this.getByLendItemNo(agentBillNo);
lendItem.setStatus(1);//状态为已支付
baseMapper.updateById(lendItem);
//修改标的记录,标的信息字段投资人数,已投金额修改lend
Long lendId = lendItem.getLendId();
Lend lend = lendMapper.selectById(lendId);
lend.setInvestNum(lend.getInvestNum()+1);
lend.setInvestAmount(lend.getInvestAmount().add(lendItem.getInvestAmount()));
lendMapper.updateById(lend);
//新增交易流水
//新增交易流水
TransFlowBO transFlowBO = new TransFlowBO(
agentBillNo,
voteBindCode,
new BigDecimal(voteAmt),
TransTypeEnum.INVEST_LOCK,
"投资项目编号:" + lend.getLendNo() + ",项目名称:" + lend.getTitle());
transFlowService.saveTransFlow(transFlowBO);
}
/**
* @param lendItemNo:
* @return LendItem
* @author Likejin
* @description 根据lend_item_no查询具体的lend_item
* @date 2023/4/18 11:06
*/
private LendItem getByLendItemNo(String lendItemNo){
QueryWrapper<LendItem> lendItemQueryWrapper = new QueryWrapper<>();
lendItemQueryWrapper.eq("lend_item_no", lendItemNo);
return baseMapper.selectOne(lendItemQueryWrapper);
}
}
- 其他调用的service
com/atguigu/srb/core/service/UserBindService.java
String getBindCodeByUserId(Long userId);
/**
* @param userId:
* @return String
* @author Likejin
* @description 根据user_id获取到user_bind
* @date 2023/4/18 11:35
*/
@Override
public String getBindCodeByUserId(Long userId) {
QueryWrapper<UserBind> userBindQueryWrapper = new QueryWrapper<>();
QueryWrapper<UserBind> user_id = userBindQueryWrapper.eq("user_id", userId);
UserBind userBind = baseMapper.selectOne(userBindQueryWrapper);
return userBind.getBindCode();
}
④代码逻辑
- 返回组装表单
目的:利用请求数据(标的id和投资金额)返回汇付宝所需表单,保存一条lend_item
业务流程:
利用token获取到用户user_id,经过一系列的健壮性校验,封装lend_item数据库表需要的数据,封装汇付宝表单需要的数据。 - 回调
目的:利用请求数据一系列表返回sucess
业务流程:
校验sign签名,判断幂等性,修改账户金额user_account,修改投资记录状态lend_item,修改标的信息lend,新增交易流水trans_flow