21、借款申请
21.1、需求介绍
21.1.1、借款人申请借款
1、需求描述
2、相关数据库表
21.1.2、具体步骤
step1:借款人点击“我要借款”
step2:展示借款信息填写页面
step3:借款人填写信息并提交
step4:展示等待审核页面
step5:平台审核
step6:显示审批结果
未通过
通过
21.2、获取借款额度
21.2.1、获取借款额度
1、实现思路
(1)获取借款人积分
(2)根据积分获取借款额度,借款人每次借款不能超过借款额度
2、Controller
BorrowInfoController
package com.atguigu.srb.core.controller.api;
@Api(tags = "借款信息")
@RestController
@RequestMapping("/api/core/borrowInfo")
@Slf4j
public class BorrowInfoController {
@Resource
private BorrowInfoService borrowInfoService;
@ApiOperation("获取借款额度")
@GetMapping("/auth/getBorrowAmount")
public R getBorrowAmount(HttpServletRequest request) {
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
BigDecimal borrowAmount = borrowInfoService.getBorrowAmount(userId);
return R.ok().data("borrowAmount", borrowAmount);
}
}
3、Service
接口:BorrowInfoService
BigDecimal getBorrowAmount(Long userId);
实现:BorrowInfoServiceImpl
package com.atguigu.srb.core.service.impl;
@Service
public class BorrowInfoServiceImpl extends ServiceImpl<BorrowInfoMapper, BorrowInfo> implements BorrowInfoService {
@Resource
private UserInfoMapper userInfoMapper;
@Resource
private IntegralGradeMapper integralGradeMapper;
@Override
public BigDecimal getBorrowAmount(Long userId) {
//获取用户积分
UserInfo userInfo = userInfoMapper.selectById(userId);
Assert.notNull(userInfo, ResponseEnum.LOGIN_MOBILE_ERROR);
Integer integral = userInfo.getIntegral();
//根据积分查询借款额度
QueryWrapper<IntegralGrade> queryWrapper = new QueryWrapper<>();
queryWrapper.le("integral_start", integral);
queryWrapper.ge("integral_end", integral);
IntegralGrade integralGradeConfig = integralGradeMapper.selectOne(queryWrapper);
if(integralGradeConfig == null){
return BigDecimal.ZERO;
}
return integralGradeConfig.getBorrowAmount();
}
}
21.2.2、借款入口前端
pages/user/borrower.vue
<NuxtLink to="/user/apply" v-if="borrowerStatus === 2">
<el-button style="margin-top:20px;" type="success">
我要借款
</el-button>
</NuxtLink>
21.2.3、借款申请
1、页面
pages/user/apply.vue
2、获取下拉列表
methods中定义
methods: {
//初始化下拉列表的数据
initSelected() {
//还款方式列表
this.$axios
.$get('/api/core/dict/findByDictCode/returnMethod')
.then((response) => {
this.returnMethodList = response.data.dictList
})
//资金用途列表
this.$axios
.$get('/api/core/dict/findByDictCode/moneyUse')
.then((response) => {
this.moneyUseList = response.data.dictList
})
},
},
mounted中调用
mounted() {
//初始化下拉列表
this.initSelected()
},
3、获取借款额度
methods中定义
//获取借款额度
getBorrowAmount() {
this.$axios
.$get('/api/core/borrowInfo/auth/getBorrowAmount')
.then((response) => {
this.borrowAmount = response.data.borrowAmount
})
},
created中调用
mounted() {
//获取借款额度
this.getBorrowAmount()
//初始化下拉列表
......
},
4、判断借款额度
借款人每次借款不能超过借款额度
watch: {
'borrowInfo.amount'(value) {
if (value > this.borrowAmount) {
let _this = this
this.$alert('您的借款额度不足!', {
type: 'error',
callback() {
_this.borrowInfo.amount = _this.borrowAmount
},
})
}
},
},
21.3、提交借款申请
21.3.1、后端实现
1、实现思路
借款人提交借款要判断借款人账户绑定状态与借款人信息审批状态,只有这两个状态都成立才能借款,这两个状态都在会员表中
目标:将借款申请表单中用户填写的数据保存在borrow_info数据库表中
2、枚举
BorrowInfoStatusEnum
NO_AUTH(0, "未提交"),
CHECK_RUN(1, "审核中"),
CHECK_OK(2, "审核通过"),
CHECK_FAIL(-1, "审核不通过"),
;
状态(0:未提交,1:审核中, 2:审核通过, -1:审核不通过)
3、Controller
BorrowInfoController
@ApiOperation("提交借款申请")
@PostMapping("/auth/save")
public R save(@RequestBody BorrowInfo borrowInfo, HttpServletRequest request) {
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
borrowInfoService.saveBorrowInfo(borrowInfo, userId);
return R.ok().message("提交成功");
}
4、Service
接口:BorrowInfoService
void saveBorrowInfo(BorrowInfo borrowInfo, Long userId);
实现:BorrowInfoServiceImpl
@Override
public void saveBorrowInfo(BorrowInfo borrowInfo, Long userId) {
//获取userInfo的用户数据
UserInfo userInfo = userInfoMapper.selectById(userId);
//判断用户绑定状态
Assert.isTrue(
userInfo.getBindStatus().intValue() == UserBindEnum.BIND_OK.getStatus().intValue(),
ResponseEnum.USER_NO_BIND_ERROR);
//判断用户信息是否审批通过
Assert.isTrue(
userInfo.getBorrowAuthStatus().intValue() == BorrowerStatusEnum.AUTH_OK.getStatus().intValue(),
ResponseEnum.USER_NO_AMOUNT_ERROR);
//判断借款额度是否足够
BigDecimal borrowAmount = this.getBorrowAmount(userId);
Assert.isTrue(
borrowInfo.getAmount().doubleValue() <= borrowAmount.doubleValue(),
ResponseEnum.USER_AMOUNT_LESS_ERROR);
//存储数据
borrowInfo.setUserId(userId);
// 校验年化利率(此处真实开发中,应该限制用户最大输入的小数点位数,并且在数据库中做足够大的小数点位数存储,否则计算利息的时候会出现精度不足导致的一系列问题)
String borrowYearRate = borrowInfo.getBorrowYearRate().toPlainString();
String[] borrowYearRateSplit = borrowYearRate.split("\\.");
if (borrowYearRateSplit.length >1 && borrowYearRateSplit[1].length() > 2) {
throw new BusinessException("年化利率仅支持到小数点后两位");
}
// 百分比转成小数(数据库类型修改为decimal(10,4))
borrowInfo.setBorrowYearRate(borrowInfo.getBorrowYearRate().divide(new BigDecimal(100), 4, RoundingMode.DOWN));
borrowInfo.setStatus(BorrowInfoStatusEnum.CHECK_RUN.getStatus());
baseMapper.insert(borrowInfo);
}
21.3.2、前端
提交借款申请
pages/user/apply.vue
//提交借款申请
save() {
// this.submitBtnDisabled = true
this.$axios
.$post('/api/core/borrowInfo/auth/save', this.borrowInfo)
.then((response) => {
this.active = 1
})
},
21.4、获取借款申请状态
21.4.1、获取借款状态
1、BorrowInfoController
@ApiOperation("获取借款申请审批状态")
@GetMapping("/auth/getBorrowInfoStatus")
public R getBorrowerStatus(HttpServletRequest request){
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
Integer status = borrowInfoService.getStatusByUserId(userId);
return R.ok().data("borrowInfoStatus", status);
}
2、service
接口:BorrowInfoService
Integer getStatusByUserId(Long userId);
实现:BorrowInfoServiceImpl
@Override
public Integer getStatusByUserId(Long userId) {
QueryWrapper<BorrowInfo> borrowInfoQueryWrapper = new QueryWrapper<>();
borrowInfoQueryWrapper.select("status").eq("user_id", userId);
List<Object> objects = baseMapper.selectObjs(borrowInfoQueryWrapper);
if(objects.size() == 0){
//借款人尚未提交信息
return BorrowInfoStatusEnum.NO_AUTH.getStatus();
}
Integer status = (Integer)objects.get(0);
return status;
}
21.4.2、前端开发
脚本
pages/user/apply.vue
将this.getBorrowAmount()和this.initSelected()移植到this.getBorrowInfoStatus()中
mounted() {
//获取审批状态
this.getBorrowInfoStatus()
},
methods中添加方法:
//获取借款审批状态
getBorrowInfoStatus() {
this.$axios
.$get('/api/core/borrowInfo/auth/getBorrowInfoStatus')
.then((response) => {
this.borrowInfoStatus = response.data.borrowInfoStatus
if (this.borrowInfoStatus === 0) {
//未认证
this.active = 0
//获取借款额度
this.getBorrowAmount()
//初始化下拉列表
this.initSelected()
} else if (this.borrowInfoStatus === 1) {
//审批中
this.active = 1
} else if (this.borrowInfoStatus === 2) {
//审批成功
this.active = 2
} else if (this.borrowInfoStatus === -1) {
//审批失败
this.active = 2
}
})
},
将 data() 中 active的初始化值设置为null
active: null, //步骤
22、借款审核
22.1、借款信息列表
22.1.1、需求
22.1.2、后端实现
1、扩展实体对象
列表的结果需要关联查询,数据字典的数据也需要展示对应的文本内容而不是值,除了定义VO的方式,我们也可以使用扩展实体类的方式
在BorrowInfo类中扩展以下字段
//扩展字段
@ApiModelProperty(value = "姓名")
@TableField(exist = false)
private String name;
@ApiModelProperty(value = "手机")
@TableField(exist = false)
private String mobile;
@ApiModelProperty(value = "其他参数")
@TableField(exist = false)
private Map<String,Object> param = new HashMap<>();
2、Controller
添加 AdminBorrowInfoController
package com.atguigu.srb.core.controller.admin;
@Api(tags = "借款管理")
@RestController
@RequestMapping("/admin/core/borrowInfo")
@Slf4j
public class AdminBorrowInfoController {
@Resource
private BorrowInfoService borrowInfoService;
@ApiOperation("借款信息列表")
@GetMapping("/list")
public R list() {
List<BorrowInfo> borrowInfoList = borrowInfoService.selectList();
return R.ok().data("list", borrowInfoList);
}
}
3、Service
接口:BorrowInfoService
List<BorrowInfo> selectList();
实现:BorrowInfoServiceImpl
@Resource
private DictService dictService;
@Override
public List<BorrowInfo> selectList() {
List<BorrowInfo> borrowInfoList = baseMapper.selectBorrowInfoList();
borrowInfoList.forEach(borrowInfo -> {
String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", borrowInfo.getReturnMethod());
String moneyUse = dictService.getNameByParentDictCodeAndValue("moneyUse", borrowInfo.getMoneyUse());
String status = BorrowInfoStatusEnum.getMsgByStatus(borrowInfo.getStatus());
borrowInfo.getParam().put("returnMethod", returnMethod);
borrowInfo.getParam().put("moneyUse", moneyUse);
borrowInfo.getParam().put("status", status);
});
return borrowInfoList;
}
4、Mapper
接口:BorrowInfoMapper
List<BorrowInfo> selectBorrowInfoList();
xml:BorrowInfoMapper.xml
<select id="selectBorrowInfoList" resultType="com.atguigu.srb.core.pojo.entity.BorrowInfo">
SELECT
bi.*,
b.name,
b.mobile
FROM
borrow_info AS bi
LEFT JOIN borrower AS b ON bi.user_id = b.user_id
WHERE bi.is_deleted = 0
</select>
22.1.3、前端
1、创建页面组件
创建 src/views/core/borrow-info/list.vue
<template>
<div class="app-container">
借款列表
</div>
</template>
<script>
export default {
}
</script>
创建 src/views/core/borrow-info/detail.vue
<template>
<div class="app-container">
借款详情
</div>
</template>
<script>
export default {
}
</script>
2、配置路由
src/router/index.js
在“借款管理”下添加子路由
{
path: 'info-list',
name: 'coreBorrowInfoList',
component: () => import('@/views/core/borrow-info/list'),
meta: { title: '借款列表' }
},
{
path: 'info-detail/:id',
name: 'coreBorrowInfoDetail',
component: () => import('@/views/core/borrow-info/detail'),
meta: { title: '借款详情' },
hidden: true
}
3、定义api
创建 src/api/core/borrow-info.js
import request from '@/utils/request'
export default {
getList() {
return request({
url: `/admin/core/borrowInfo/list`,
method: 'get'
})
}
}
4、页面脚本
src/views/core/borrow-info/list.vue
<script>
import borrowInfoApi from '@/api/core/borrow-info'
export default {
data() {
return {
list: null // 列表
}
},
created() {
this.fetchData()
},
methods: {
// 加载列表数据
fetchData() {
borrowInfoApi.getList().then(response => {
this.list = response.data.list
})
}
}
}
</script>
5、页面模板
src/views/core/borrow-info/list.vue
<template>
<div class="app-container">
<!-- 列表 -->
<el-table :data="list" stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="name" label="借款人姓名" width="90" />
<el-table-column prop="mobile" label="手机" />
<el-table-column prop="amount" label="借款金额" />
<el-table-column label="借款期限" width="90">
<template slot-scope="scope">{{ scope.row.period }}个月</template>
</el-table-column>
<el-table-column prop="param.returnMethod" label="还款方式" width="150" />
<el-table-column prop="param.moneyUse" label="资金用途" width="100" />
<el-table-column label="年化利率" width="90">
<template slot-scope="scope">
{{ scope.row.borrowYearRate * 100 }}%
</template>
</el-table-column>
<el-table-column prop="param.status" label="状态" width="100" />
<el-table-column prop="createTime" label="申请时间" width="150" />
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<el-button type="primary" size="mini">
<router-link :to="'/core/borrower/info-detail/' + scope.row.id">
查看
</router-link>
</el-button>
<el-button
v-if="scope.row.status === 1"
type="warning"
size="mini"
@click="approvalShow(scope.row)"
>
审批
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
22.2、借款详情
22.2.1、需求
借款详情展示借款信息与借款人信息
22.2.2、后端实现
1、Controller
AdminBorrowInfoController
@ApiOperation("获取借款信息")
@GetMapping("/show/{id}")
public R show(
@ApiParam(value = "借款id", required = true)
@PathVariable Long id) {
Map<String, Object> borrowInfoDetail = borrowInfoService.getBorrowInfoDetail(id);
return R.ok().data("borrowInfoDetail", borrowInfoDetail);
}
2、Service
接口:BorrowInfoService
Map<String, Object> getBorrowInfoDetail(Long id);
实现:BorrowInfoServiceImpl
@Resource
private BorrowerMapper borrowerMapper;
@Resource
private BorrowerService borrowerService;
@Override
public Map<String, Object> getBorrowInfoDetail(Long id) {
//查询借款对象
BorrowInfo borrowInfo = baseMapper.selectById(id);
//组装数据
String returnMethod = dictService.getNameByParentDictCodeAndValue("returnMethod", borrowInfo.getReturnMethod());
String moneyUse = dictService.getNameByParentDictCodeAndValue("moneyUse", borrowInfo.getMoneyUse());
String status = BorrowInfoStatusEnum.getMsgByStatus(borrowInfo.getStatus());
borrowInfo.getParam().put("returnMethod", returnMethod);
borrowInfo.getParam().put("moneyUse", moneyUse);
borrowInfo.getParam().put("status", status);
//根据user_id获取借款人对象
QueryWrapper<Borrower> borrowerQueryWrapper = new QueryWrapper<Borrower>();
borrowerQueryWrapper.eq("user_id", borrowInfo.getUserId());
Borrower borrower = borrowerMapper.selectOne(borrowerQueryWrapper);
//组装借款人对象
BorrowerDetailVO borrowerDetailVO = borrowerService.getBorrowerDetailVOById(borrower.getId());
//组装数据
Map<String, Object> result = new HashMap<>();
result.put("borrowInfo", borrowInfo);
result.put("borrower", borrowerDetailVO);
return result;
}
22.2.3、前端
1、定义api
src/api/core/borrow-info.js
show(id) {
return request({
url: `/admin/core/borrowInfo/show/${id}`,
method: 'get'
})
}
2、添加自定义css
src/styles/show.css
3、页面脚本
src/views/core/borrow-info/detail.vue
<script>
import borrowInfoApi from '@/api/core/borrow-info'
import '@/styles/show.css'
export default {
data() {
return {
borrowInfoDetail: {
borrowInfo: {
param: {}
},
borrower: {}
}
}
},
created() {
if (this.$route.params.id) {
this.fetchDataById()
}
},
methods: {
fetchDataById() {
borrowInfoApi.show(this.$route.params.id).then(response => {
this.borrowInfoDetail = response.data.borrowInfoDetail
})
},
back() {
this.$router.push({ path: '/core/borrower/info-list' })
}
}
}
</script>
4、页面模板
src/views/core/borrow-info/detail.vue
<template>
<div class="app-container">
<h4>借款信息</h4>
<table
class="table table-striped table-condenseda table-bordered"
width="100%"
>
<tbody>
<tr>
<th width="15%">借款金额</th>
<td width="35%">{{ borrowInfoDetail.borrowInfo.amount }}元</td>
<th width="15%">借款期限</th>
<td width="35%">{{ borrowInfoDetail.borrowInfo.period }}个月</td>
</tr>
<tr>
<th>年化利率</th>
<td>{{ borrowInfoDetail.borrowInfo.borrowYearRate * 100 }}%</td>
<th>还款方式</th>
<td>{{ borrowInfoDetail.borrowInfo.param.returnMethod }}</td>
</tr>
<tr>
<th>资金用途</th>
<td>{{ borrowInfoDetail.borrowInfo.param.moneyUse }}</td>
<th>状态</th>
<td>{{ borrowInfoDetail.borrowInfo.param.status }}</td>
</tr>
<tr>
<th>创建时间</th>
<td>{{ borrowInfoDetail.borrowInfo.createTime }}</td>
<th></th>
<td></td>
</tr>
</tbody>
</table>
<h4>借款人信息</h4>
<table
class="table table-striped table-condenseda table-bordered"
width="100%"
>
<tbody>
<tr>
<th width="15%">借款人</th>
<td width="35%">
<b>{{ borrowInfoDetail.borrower.name }}</b>
</td>
<th width="15%">手机</th>
<td width="35%">{{ borrowInfoDetail.borrower.mobile }}</td>
</tr>
<tr>
<th>身份证</th>
<td>{{ borrowInfoDetail.borrower.idCard }}</td>
<th>性别</th>
<td>{{ borrowInfoDetail.borrower.sex }}</td>
</tr>
<tr>
<th>年龄</th>
<td>{{ borrowInfoDetail.borrower.age }}</td>
<th>是否结婚</th>
<td>{{ borrowInfoDetail.borrower.marry }}</td>
</tr>
<tr>
<th>学历</th>
<td>{{ borrowInfoDetail.borrower.education }}</td>
<th>行业</th>
<td>{{ borrowInfoDetail.borrower.industry }}</td>
</tr>
<tr>
<th>月收入</th>
<td>{{ borrowInfoDetail.borrower.income }}</td>
<th>还款来源</th>
<td>{{ borrowInfoDetail.borrower.returnSource }}</td>
</tr>
<tr>
<th>创建时间</th>
<td>{{ borrowInfoDetail.borrower.createTime }}</td>
<th>状态</th>
<td>{{ borrowInfoDetail.borrower.status }}</td>
</tr>
</tbody>
</table>
<el-row style="text-align:center;margin-top: 40px;">
<el-button @click="back">
返回
</el-button>
</el-row>
</div>
</template>
22.3、借款审批
22.3.1、需求
管理平台借款审批,审批通过后产生标的,审批前我们需要跟借款人进行电话沟通,确定借款年化和平台服务费率(平台收益),借款年化可能根据实际情况调高或调低;起息日通常我们把它确定为募集结束时间(或放款时间)
22.3.2、后端实现
1、定义VO对象
package com.atguigu.srb.core.pojo.vo;
@Data
@ApiModel(description = "借款信息审批")
public class BorrowInfoApprovalVO {
@ApiModelProperty(value = "id")
private Long id;
@ApiModelProperty(value = "状态")
private Integer status;
@ApiModelProperty(value = "审批内容")
private String content;
@ApiModelProperty(value = "标题")
private String title;
@ApiModelProperty(value = "年化利率")
private BigDecimal lendYearRate;
@ApiModelProperty(value = "平台服务费率")
private BigDecimal serviceRate;
@ApiModelProperty(value = "开始日期")
private String lendStartDate;
@ApiModelProperty(value = "描述信息")
private String lendInfo;
}
2、Controller
AdminBorrowInfoController
@ApiOperation("审批借款信息")
@PostMapping("/approval")
public R approval(@RequestBody BorrowInfoApprovalVO borrowInfoApprovalVO) {
borrowInfoService.approval(borrowInfoApprovalVO);
return R.ok().message("审批完成");
}
3、Service
接口:BorrowInfoService
void approval(BorrowInfoApprovalVO borrowInfoApprovalVO);
实现:BorrowInfoServiceImpl
@Transactional(rollbackFor = Exception.class)
@Override
public void approval(BorrowInfoApprovalVO borrowInfoApprovalVO) {
//修改借款信息状态
Long borrowInfoId = borrowInfoApprovalVO.getId();
BorrowInfo borrowInfo = baseMapper.selectById(borrowInfoId);
borrowInfo.setStatus(borrowInfoApprovalVO.getStatus());
borrowInfo.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(borrowInfo);
//审核通过则创建标的
if (borrowInfoApprovalVO.getStatus().intValue() == BorrowInfoStatusEnum.CHECK_OK.getStatus().intValue()) {
//创建标的
//TODO
}
}
22.3.3、前端实现
1、定义api
src/api/core/borrow-info.js
approval(borrowInfoApproval) {
return request({
url: '/admin/core/borrowInfo/approval',
method: 'post',
data: borrowInfoApproval
})
}
2、页面脚本
src/views/core/borrow-info/list.vue
data:
dialogVisible: false, //审批对话框
borrowInfoApproval: {
status: 2,
serviceRate: 5,
lendYearRate: 0 //初始化,解决表单中数据修改时无法及时渲染的问题
} //审批对象
methods:
approvalShow(row) {
this.dialogVisible = true
this.borrowInfoApproval.id = row.id
this.borrowInfoApproval.lendYearRate = row.borrowYearRate * 100
},
approvalSubmit() {
borrowInfoApi.approval(this.borrowInfoApproval).then(response => {
this.dialogVisible = false
this.$message.success(response.message)
this.fetchData()
})
}
3、页面模板
src/views/core/borrow-info/list.vue
<!-- 审批对话框 -->
<el-dialog title="审批" :visible.sync="dialogVisible" width="490px">
<el-form label-position="right" label-width="100px">
<el-form-item label="是否通过">
<el-radio-group v-model="borrowInfoApproval.status">
<el-radio :label="2">通过</el-radio>
<el-radio :label="-1">不通过</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="borrowInfoApproval.status == 2" label="标的名称">
<el-input v-model="borrowInfoApproval.title" />
</el-form-item>
<el-form-item v-if="borrowInfoApproval.status == 2" label="起息日">
<el-date-picker
v-model="borrowInfoApproval.lendStartDate"
type="date"
placeholder="选择开始时间"
value-format="yyyy-MM-dd"
/>
</el-form-item>
<el-form-item v-if="borrowInfoApproval.status == 2" label="年化收益率">
<el-input v-model="borrowInfoApproval.lendYearRate">
<template slot="append">%</template>
</el-input>
</el-form-item>
<el-form-item v-if="borrowInfoApproval.status == 2" label="服务费率">
<el-input v-model="borrowInfoApproval.serviceRate">
<template slot="append">%</template>
</el-input>
</el-form-item>
<el-form-item v-if="borrowInfoApproval.status == 2" label="标的描述">
<el-input v-model="borrowInfoApproval.lendInfo" type="textarea" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">
取消
</el-button>
<el-button type="primary" @click="approvalSubmit">
确定
</el-button>
</div>
</el-dialog>
本文章参考B站 尚硅谷《尚融宝》Java微服务分布式金融项目,仅供个人学习使用,部分内容为本人自己见解,与尚硅谷无关。