核心业务2:借款人申请借款额度
1.业务流程图
------------截止提交个人信息部分--------
2.借款人申请借款额度数据库设计
3.借款人申请额度流程
4.前端代码逻辑
5.后端代码逻辑
------------截止提交个人信息部分--------
核心业务2:借款人申请借款额度
1.业务流程图
①用户在注册登陆账户绑定之后,点击立即借款进入到借款认证页
- A进入到借款认证页后,进入尚融宝判断借款状态来显示步骤
- B表单中的下拉列表数据在尚融宝查询数据字典获取
- C在上传照片时利用尚融宝上传数据到aliyun,在删除图片后删除aliyun中数据
- D点击提交后提交表单到尚融宝更新借款人数据
②如果A中已提交未审核则进入审核页
③如果A中已审核则将结果返回
2.借款人申请借款额度数据库设计
①用户数据表user_info
②借款人表borrower
③借款人绑定表borrower_attach
④三张表关系
- user_info通过user_id和borrower的user_id关联
- borrower的id和borrower_attach的borrow_id关联
- user_info即如果用户为借款人申请借款额度,用户信息表中包含了该用户为借款人的认证状态。
- borrower即包含了用户为借款人的基本信息
- borrower_attach表包含了用户为借款人的四张图片信息(房产证,轿车证,身份证正反面)
3.借款人申请额度流程
①前端
- 用户注册登陆绑定数据后
- 第一次借款认证填写信息提交表单
- 提交后再次访问提交表单页则直接进入认证中或者认证结果
②尚融宝后端
- 用户访问认证页
- 判断用户是否认证过,认证的状态。
- 如果没有认证过则前端显示表单页,后端需要在表单页显示时返回数据字典来初始化表单的下拉列表。
- 在填写表单时对图片的上传和删除同步到aliyun的oss
- 在填写完表单之后前端提交表单,后端根据表单数据更新user_info的借款人认证状态,根据token获得user_id以及表单信息来更新borrower和borrower_attach
4.代码逻辑
①前端对应表单对象
②前端请求接口
- 判断借款人认证状态
- 请求获取数据字典
- 请求上传和删除图片
- 提交表单
③前端复杂逻辑
- 需要完善表单信息。
文件的表单信息需要上传成功后加入到表单中,如果删除文件,对应的也需要删除表单中的文件(删除文件如果删除表单,组件封装了调用成功后的响应结果) - 刚进入认证页会先进入表单页再判断是否认证过,前端就算认证过也会闪过表单。
修改步骤为null,必须查看认证结果后赋值,不会闪表单
④总体代码
- pages/user/borrow.vue
<template>
<div class="personal-main">
<div class="personal-pay">
<h3><i>借款人信息认证</i></h3>
<!--步骤导航-->
<el-steps :active="active" style="margin: 40px">
<el-step title="填写借款人信息"></el-step>
<el-step title="提交平台审核"></el-step>
<el-step title="等待认证结果"></el-step>
</el-steps>
<!--第一步-->
<div v-if="active === 0" class="user-borrower">
<!--个人信息-->
<h6>个人基本信息</h6>
<el-form label-width="120px">
<el-form-item label="年龄">
<el-col :span="5">
<el-input v-model="borrower.age" />
</el-col>
</el-form-item>
<el-form-item label="性别">
<el-select v-model="borrower.sex">
<el-option :value="1" :label="'男'" />
<el-option :value="0" :label="'女'" />
</el-select>
</el-form-item>
<el-form-item label="婚否">
<el-select v-model="borrower.marry">
<el-option :value="true" :label="'是'" />
<el-option :value="false" :label="'否'" />
</el-select>
</el-form-item>
<el-form-item label="学历">
<el-select v-model="borrower.education">
<el-option
v-for="item in educationList"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="行业">
<el-select v-model="borrower.industry">
<el-option
v-for="item in industryList"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="月收入">
<el-select v-model="borrower.income">
<el-option
v-for="item in incomeList"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="还款来源">
<el-select v-model="borrower.returnSource">
<el-option
v-for="item in returnSourceList"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<!--联系人信息-->
<h6>联系人信息</h6>
<el-form label-width="120px">
<el-form-item label="联系人姓名">
<el-col :span="5">
<el-input v-model="borrower.contactsName" />
</el-col>
</el-form-item>
<el-form-item label="联系人手机">
<el-col :span="5">
<el-input v-model="borrower.contactsMobile" />
</el-col>
</el-form-item>
<el-form-item label="联系人关系">
<el-select v-model="borrower.contactsRelation">
<el-option
v-for="item in contactsRelationList"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
<!--身份认证信息-->
<h6>身份认证信息</h6>
<!--其中的module是为了区分每个上传文件的位置在OSS中-->
<el-form label-width="120px">
<el-form-item label="身份证人像面">
<el-upload
:on-success="onUploadSuccessIdCard1"
:on-remove="onUploadRemove"
:multiple="false"
:action="uploadUrl"
:data="{ module: 'idCard1' }"
:limit="1"
list-type="picture-card"
>
<i class="el-icon-plus"></i>
</el-upload>
</el-form-item>
<el-form-item label="身份证国徽面">
<el-upload
:on-success="onUploadSuccessIdCard2"
:on-remove="onUploadRemove"
:multiple="false"
:action="uploadUrl"
:data="{ module: 'idCard2' }"
:limit="1"
list-type="picture-card"
>
<i class="el-icon-plus"></i>
</el-upload>
</el-form-item>
</el-form>
<!--其他信息-->
<h6>其他信息</h6>
<el-form label-width="120px">
<el-form-item label="房产信息">
<el-upload
:on-success="onUploadSuccessHouse"
:on-remove="onUploadRemove"
:multiple="false"
:action="uploadUrl"
:data="{ module: 'house' }"
list-type="picture-card"
>
<i class="el-icon-plus"></i>
</el-upload>
</el-form-item>
<el-form-item label="车辆信息">
<el-upload
:on-success="onUploadSuccessCar"
:on-remove="onUploadRemove"
:multiple="false"
:action="uploadUrl"
:data="{ module: 'car' }"
list-type="picture-card"
>
<i class="el-icon-plus"></i>
</el-upload>
</el-form-item>
</el-form>
<el-form label-width="120px">
<el-form-item>
<el-button
type="primary"
:disabled="submitBtnDisabled"
@click="save"
>
提交
</el-button>
</el-form-item>
</el-form>
</div>
<!--第二步-->
<div v-if="active === 1">
<div style="margin-top:40px;">
<el-alert
title="您的认证申请已成功提交,请耐心等待"
type="warning"
show-icon
:closable="false"
>
我们将在2小时内完成审核,审核时间为周一至周五8:00至20:00。
</el-alert>
</div>
</div>
<!--第三步-->
<div v-if="active === 2">
<div style="margin-top:40px;">
<el-alert
v-if="borrowerStatus === 2"
title="您的认证审批已通过"
type="success"
show-icon
:closable="false"
>
</el-alert>
<el-alert
v-if="borrowerStatus === -1"
title="您的认证审批未通过"
type="error"
show-icon
:closable="false"
>
</el-alert>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
let BASE_API = process.env.BASE_API
return {
//为什么不设置为0?
//因为只有当判断完成用户的认证状态才能确定在哪一步,如果写0的话如果用户状态为待审核由于ajax异步操作,会闪一下第一个列表
active: null, //步骤
borrowerStatus: null, //认证状态
submitBtnDisabled: false, //提交按钮的修饰,防止表单重复提交,点击save然后不能提交
//借款人信息
borrower: {
borrowerAttachList: [], //存储文件数据的基本信息
},
educationList: [], //学历列表
industryList: [], //行业列表
incomeList: [], //月收入列表
returnSourceList: [], //还款来源列表
contactsRelationList: [], //联系人关系
uploadUrl: BASE_API + '/api/oss/file/upload', //文件上传地址
}
},
methods: {
//保存表单提交的信息
save() {
// debugger
this.submitBtnDisabled = true
this.$axios
.$post('/api/core/borrower/auth/save', this.borrower)
.then((response) => {
this.active = 1
})
},
//根据上传文件得到的结果,封装borrowerAttachList存储文件的基本信息(响应结果,文件参数)
onUploadSuccessIdCard1(response, file) {
this.onUploadSuccess(response, file, 'idCard1')
},
onUploadSuccessIdCard2(response, file) {
this.onUploadSuccess(response, file, 'idCard2')
},
onUploadSuccessHouse(response, file) {
this.onUploadSuccess(response, file, 'house')
},
onUploadSuccessCar(response, file) {
this.onUploadSuccess(response, file, 'car')
},
onUploadSuccess(response, file, type) {
// debugger
if (response.code !== 0) {
//上传失败,返回响应结果
this.$message.error(response.message)
return
}
// 上传成功,填充上传文件列表
this.borrower.borrowerAttachList.push({
imageName: file.name,
imageUrl: response.data.url,
imageType: type,
})
//console.log(this.borrower.borrowerAttachList)
},
//将oss系统删除文件(文件参数)file参数组装了上传的响应结果
onUploadRemove(file, fileList) {
console.log('file', file)
console.log('fileList', fileList)
//删除oss服务器上的内容
//调用远程的删除接口
this.$axios
.$delete('/api/oss/file/remove?url=' + file.response.data.url)
.then((response) => {
// debugger
console.log('远程删除文件成功')
//从 this.borrower.borrowerAttachList列表中删除该文件
//将过滤的结果回填到原来列表
this.borrower.borrowerAttachList = this.borrower.borrowerAttachList.filter(
function(item) {
console.log('item', item)
//两个地址相等则返回true,不删除
return item.imageUrl != file.response.data.url
}
)
})
},
//根据查询的数据字典初始化下拉列表
initSelected() {
//学历列表
this.$axios
.$get('/api/core/dict/findByDictCode/education')
.then((response) => {
this.educationList = response.data.dictList
})
//行业列表
this.$axios
.$get('/api/core/dict/findByDictCode/industry')
.then((response) => {
this.industryList = response.data.dictList
})
//收入列表
this.$axios
.$get('/api/core/dict/findByDictCode/income')
.then((response) => {
this.incomeList = response.data.dictList
})
//还款来源列表
this.$axios
.$get('/api/core/dict/findByDictCode/returnSource')
.then((response) => {
this.returnSourceList = response.data.dictList
})
//联系人关系列表
this.$axios
.$get('/api/core/dict/findByDictCode/relation')
.then((response) => {
this.contactsRelationList = response.data.dictList
})
},
getUserInfo() {
this.$axios
.$get('/api/core/borrower/auth/getBorrowerStatus')
.then((response) => {
this.borrowerStatus = response.data.borrowerStatus
if (this.borrowerStatus == 0) {
//未认证,展示下拉列表
this.active = 0
this.initSelected()
} else if (this.borrowerStatus == 1) {
//审批中
this.active = 1
} else if (this.borrowerStatus == 2) {
//审批通过
this.active = 2
} else if (this.borrowerStatus == -1) {
//审批没通过
this.active = 2
}
})
},
},
created() {
//更改为判断借款状态
this.getUserInfo()
},
}
</script>
5.后端代码逻辑
①前端调用的接口
- 开发接口
- 调用的接口
②后端复杂逻辑
- 获取下拉列表时根据dict_code去查对应的子节点
需要先查询到dict_code的id然后根据此id去查父id为此id的数据返回(调用之前写的根据父id查询的service) - 保存存款人信息时需要获取user_id绑定
即需要用户先登录,此时可以从token中解析出user_id - 根据表单信息更新借款人表单时
需要先根据user_id查询出user_info的基本信息,补充表单信息更新borrower表 - 根据表单信息更新借款人附件表单时
需要先查询借款人的id然后在附件表单中绑定借款人id - 提交表单后也需要更新user_info的借款人认证状态
- 获取借款人认证状态
可以从已登录的token中获取。然后根据id从borrower表中查询status,查询出数据则说明不是未认证返回status(1认证中2认证成功-1认证失败),查询不出数据则是未认证返回status(0未认证)
③后端创建的接受前端对象
package com.atguigu.srb.core.pojo.vo;
import com.atguigu.srb.core.pojo.entity.BorrowerAttach;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(description="借款人认证信息")
public class BorrowerVO{
@ApiModelProperty(value = "性别(1:男 0:女)")
private Integer sex;
@ApiModelProperty(value = "年龄")
private Integer age;
@ApiModelProperty(value = "学历")
private Integer education;
@ApiModelProperty(value = "是否结婚(1:是 0:否)")
private Boolean marry;
@ApiModelProperty(value = "行业")
private Integer industry;
@ApiModelProperty(value = "月收入")
private Integer income;
@ApiModelProperty(value = "还款来源")
private Integer returnSource;
@ApiModelProperty(value = "联系人名称")
private String contactsName;
@ApiModelProperty(value = "联系人手机")
private String contactsMobile;
@ApiModelProperty(value = "联系人关系")
private Integer contactsRelation;
@ApiModelProperty(value = "借款人附件资料")
private List<BorrowerAttach> borrowerAttachList;
}
④获取数据字典业务
- controller
package com.atguigu.srb.core.controller.api;
import com.atguigu.common.result.R;
import com.atguigu.srb.core.pojo.entity.Dict;
import com.atguigu.srb.core.service.DictService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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/dict")
@Slf4j
public class DictController {
@Resource
private DictService dictService;
@ApiOperation("根据DictCode获取下级节点")
@GetMapping("/findByDictCode/{dictCode}")
public R findByDictCode(
@ApiParam("节点编码")
@PathVariable("dictCode") String dictCode){
List<Dict> list = dictService.findByDictCode(dictCode);
return R.ok().data("dictList",list);
}
}
- service(前三种方法是admin管理系统调用的Dict方法)
package com.atguigu.srb.core.service;
import com.atguigu.srb.core.pojo.dto.ExcelDictDTO;
import com.atguigu.srb.core.pojo.entity.Dict;
import com.baomidou.mybatisplus.extension.service.IService;
import java.io.InputStream;
import java.util.List;
/**
* <p>
* 数据字典 服务类
* </p>
*
* @author Likejin
* @since 2023-04-09
*/
public interface DictService extends IService<Dict> {
void importData(InputStream inputStream);
List<ExcelDictDTO> listDictData();
List<Dict> listByParentId(Long parentId);
/**
* @param dictCode:
* @return List<Dict>
* @author Likejin
* @description 根据dictCode编码查询其下级节点
* @date 2023/4/15 20:00
*/
List<Dict> findByDictCode(String dictCode);
}
com.atguigu.srb.core.service.impl.DictServiceImpl类下加入方法
/**
* @param dictCode:
* @return List<Dict>
* @author Likejin
* @description 根据dictCode编码查询其下级节点
* @date 2023/4/15 20:00
*/
@Override
public List<Dict> findByDictCode(String dictCode) {
QueryWrapper<Dict> dictQueryWrapper = new QueryWrapper<>();
dictQueryWrapper.eq("dict_code",dictCode);
Dict dict = baseMapper.selectOne(dictQueryWrapper);
//拿到父dict的id,然后根据该id去查父id等于这个id的list<Dict>
return this.listByParentId(dict.getId());
}
⑤借款人业务(获取认证状态)(保存借款人信息)
- controller
package com.atguigu.srb.core.controller.api;
import com.atguigu.common.result.R;
import com.atguigu.srb.base.util.JwtUtils;
import com.atguigu.srb.core.pojo.vo.BorrowerVO;
import com.atguigu.srb.core.service.BorrowerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@Api(tags = "借款人")
@RestController
@RequestMapping("/api/core/borrower")
@Slf4j
public class BorrowerController {
@Resource
private BorrowerService borrowerService;
@ApiOperation("保存借款人信息")
@PostMapping("/auth/save")
public R save(@RequestBody BorrowerVO borrowerVO, HttpServletRequest request) {
//获取已经登录人的id
String token = request.getHeader("token");
Long userId = JwtUtils.getUserId(token);
//存储表单信息
borrowerService.saveBorrowerVOByUserId(borrowerVO, userId);
return R.ok().message("信息提交成功");
}
@ApiOperation("获取借款人认证状态")
@GetMapping("/auth/getBorrowerStatus")
public R getBorrowerStatus(HttpServletRequest httpServletRequest){
String token = httpServletRequest.getHeader("token");
Long userId = JwtUtils.getUserId(token);
Integer status = borrowerService.getStatusByUserId(userId);
return R.ok().data("borrowerStatus",status);
}
}
- service
package com.atguigu.srb.core.service;
import com.atguigu.srb.core.pojo.entity.Borrower;
import com.atguigu.srb.core.pojo.vo.BorrowerVO;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 借款人 服务类
* </p>
*
* @author Likejin
* @since 2023-04-09
*/
public interface BorrowerService extends IService<Borrower> {
void saveBorrowerVOByUserId(BorrowerVO borrowerVO, Long userId);
Integer getStatusByUserId(Long userId);
}
package com.atguigu.srb.core.service.impl;
import com.atguigu.srb.core.enums.BorrowerStatusEnum;
import com.atguigu.srb.core.mapper.BorrowerAttachMapper;
import com.atguigu.srb.core.mapper.UserInfoMapper;
import com.atguigu.srb.core.pojo.entity.Borrower;
import com.atguigu.srb.core.mapper.BorrowerMapper;
import com.atguigu.srb.core.pojo.entity.BorrowerAttach;
import com.atguigu.srb.core.pojo.entity.UserInfo;
import com.atguigu.srb.core.pojo.vo.BorrowerVO;
import com.atguigu.srb.core.service.BorrowerService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
/**
* <p>
* 借款人 服务实现类
* </p>
*
* @author Likejin
* @since 2023-04-09
*/
@Service
public class BorrowerServiceImpl extends ServiceImpl<BorrowerMapper, Borrower> implements BorrowerService {
@Resource
private UserInfoMapper userInfoMapper;
@Resource
private BorrowerAttachMapper borrowerAttachMapper;
/**
* @param borrowerVO:
* @param userId:
* @return void
* @author Likejin
* @description 根据前端传输对象保存数据到数据库
* @date 2023/4/15 20:15
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void saveBorrowerVOByUserId(BorrowerVO borrowerVO, Long userId) {
//获取用户基本信息
UserInfo userInfo = userInfoMapper.selectById(userId);
//拷贝信息到borrower
//保存借款人信息(根据id查询用户基本信息,然后根据表单信息更新borrower表)
Borrower borrower = new Borrower();
BeanUtils.copyProperties(borrowerVO,borrower);
borrower.setUserId(userId);
borrower.setName(userInfo.getName());
borrower.setIdCard(userInfo.getIdCard());
borrower.setMobile(userInfo.getMobile());
borrower.setStatus(BorrowerStatusEnum.AUTH_RUN.getStatus());
baseMapper.insert(borrower);
//保存附件信息到borrow——attach表
List<BorrowerAttach> borrowerAttachList = borrowerVO.getBorrowerAttachList();
borrowerAttachList.forEach(borrowerAttach -> {
borrowerAttach.setBorrowerId(borrower.getId());
borrowerAttachMapper.insert(borrowerAttach);
});
//更新user_info的借款人认证状态
userInfo.setBorrowAuthStatus(BorrowerStatusEnum.AUTH_RUN.getStatus());
userInfoMapper.updateById(userInfo);
}
/**
* @param userId:
* @return Integer
* @author Likejin
* @description 借款人二次进入认证页面根据token获取认证状态
* @date 2023/4/15 20:41
*/
@Override
public Integer getStatusByUserId(Long userId) {
QueryWrapper<Borrower> borrowerQueryWrapper = new QueryWrapper<>();
borrowerQueryWrapper.select("status").eq("user_id",userId);
//selectObjs一般用于查询一个字段,list存的是status
List<Object> objects = baseMapper.selectObjs(borrowerQueryWrapper);
if(objects.size()==0){
//一条记录没取出来,说明没有提交额度信息,需要填写表单
return BorrowerStatusEnum.NO_AUTH.getStatus();
}
//取出来一条,说明已经提交了额度信息,判断此时状态
Integer status = (Integer) objects.get(0);
return status;
}
}