1. 页面原型 138
2.购买理财产品实现 139
2.1 业务接口 139
micr-api
InvestService
//购买理财产品 139
int investProduct(Integer uid, Integer productId, BigDecimal money);
2.2 业务接口实现类 139
2.2.1 查询用户账户金额 给uid的记录上锁
micr-dataservice
在mapper中定义方法FinanceAccountMapper
//购买理财产品之查询用户账户金额 给uid的记录上锁 139
FinanceAccount selectByUidForUpdate(@Param("uid") Integer uid);
编写sql FinanceAccountMapper.xml 139-140
micr-dataservice
给某一行上锁我们用for update
给u_finance_account表的uid唯一索引
<!--购买理财产品之查询用户账户金额 给uid的记录上锁*139-140-->
<select id="selectByUidForUpdate" resultMap="BaseResultMap">
select <include refid="Base_Column_List" />
from u_finance_account
where uid = #{uid}
for update
</select>
2.2.2 比较大小方法 140
micr-common
CommonUtil
/*比较BigDecimal n1 >=2 :true ,false 140*/
public static boolean ge(BigDecimal n1, BigDecimal n2){
if( n1 == null || n2 == null){
throw new RuntimeException("参数BigDecimal是null");
}
return n1.compareTo(n2) >= 0;
}
2.2.3 可以购买了 扣除账号资金 141
micr-dataservice
mapper定义方法
FinanceAccountMapper
//可以购买了 更新扣除账号资金 141
int updateAvailableMoneyByInvest(Integer uid, BigDecimal money);
编写sql
FinanceAccountMapper.xml
<!--可以购买了更新扣除账号资金 141-->
<update id="updateAvailableMoneyByInvest">
update u_finance_account set available_money = available_money - #{money}
where uid = #{uid} and ( available_money - #{money} >=0 )
</update>
2.2.4 扣除产品剩余可投资金额 141
在mapper中定义方法
ProductInfoMapper
//扣除产品剩余可投资金额 141
int updateLeftProductMoney(@Param("id") Integer productId, @Param("money") BigDecimal money);
编写sql
ProductInfoMapper.xml
<!--扣除产品剩余可投资金额 141-->
<update id="updateLeftProductMoney">
update b_product_info set left_product_money = left_product_money - #{money}
where id = #{id} and ( left_product_money - #{money} >=0 )
</update>
2.2.5 判断产品是否卖完,更新产品是满标状态 142
在mapper中定义方法
ProductInfoMapper
//更新产品是满标状态 142
int updateSelled(@Param("id") Integer productId);
编写sql
ProductInfoMapper.xml
<!--更新产品是满标状态 142-->
<update id="updateSelled">
update b_product_info set product_status = 1 , product_full_time = now()
where id = #{id}
</update>
2.2.6 实现类InvestServiceImpl 139
添加常量类 141
micr-common
InvestServiceImpl
micr-dataservice
//购买理财产品 139
@Transactional(rollbackFor = Exception.class) //143
@Override
public int investProduct(Integer uid, Integer productId, BigDecimal money) {
int result = 0;//默认,参数不正确
int rows = 0;
//1参数检查
if ((uid != null && uid > 0) && (productId != null && productId > 0)
&& (money != null && money.intValue() % 100 == 0 && money.intValue() >= 100)) {
//2.查询用户账号金额
FinanceAccount account = accountMapper.selectByUidForUpdate(uid);
if (account != null) {
//因为我们的资金的类型是 BigDecimal这个类型是不能使用><符号比较大小的,
// 所以需要我们定义一个工具类来比较大小 140
if (CommonUtil.ge(account.getAvailableMoney(), money)) {//当用户余额大于等于购买金额时为true
//资金满足购买要求
//3.检查产品是否可以购买 140
ProductInfo productInfo = productInfoMapper.selectByPrimaryKey(productId);
if (productInfo != null
&& productInfo.getProductStatus() == YLBConstant.PRODUCT_STATUS_SELLING) {
if (CommonUtil.ge(productInfo.getLeftProductMoney(), money) && //产品剩余金额大于等于购买金额
CommonUtil.ge(money, productInfo.getBidMinLimit()) && //购买金额大于等购买下线
CommonUtil.ge(productInfo.getBidMaxLimit(), money)) { //购买金额小于等购买上线
//可以购买了 4. 扣除账号资金 141
rows = accountMapper.updateAvailableMoneyByInvest(uid, money);
if (rows < 1) {
throw new RuntimeException("投资更新账号资金失败");
}
//5.扣除产品剩余可投资金额 141
rows = productInfoMapper.updateLeftProductMoney(productId, money);
if (rows < 1) {
throw new RuntimeException("投资更新产品剩余金额失败");
}
//6.创建投资记录 142
BidInfo bidInfo = new BidInfo();
bidInfo.setBidMoney(money);
bidInfo.setBidStatus(YLBConstant.INVEST_STATUS_SUCC);
bidInfo.setBidTime(new Date());
bidInfo.setProdId(productId);
bidInfo.setUid(uid);
bidInfoMapper.insertSelective(bidInfo);
//7.判断产品是否卖完,更新产品是满标状态 142
ProductInfo dbProductInfo = productInfoMapper.selectByPrimaryKey(productId);
if( dbProductInfo.getLeftProductMoney().compareTo(new BigDecimal("0")) == 0 ){
rows = productInfoMapper.updateSelled(productId);
if( rows < 1 ){
throw new RuntimeException("投资更新产品满标失败");
}
}
//8.最后这是投资成功 143
result = 1;
}
} else {
result = 4;//理财产品不存在
}
} else {
result = 3;//资金不足
}
} else {
result = 2;//资金账号不存在
}
}
return result;
}
2.3 消费者 controller 139-143
micr-web
InvestController
//购买理财产品,更新投资排行榜 139
@ApiOperation(value = "投资理财产品")
@PostMapping("/v1/invest/product")
public RespResult investProduct(
@RequestHeader("uid") Integer uid,
@RequestParam("productId") Integer productId,
@RequestParam("money") BigDecimal money){
RespResult result = RespResult.fail();
//1.检查基本参数
if( (uid != null && uid > 0) && (productId != null && productId > 0)
&&( money != null && money.intValue() % 100 == 0 && money.intValue() >= 100)){
//调用投资产品方法
int investResult = investService.investProduct(uid,productId,money);
// 143
switch (investResult){
case 0:
result.setMsg("投资数据不正确");
break;
case 1:
result = RespResult.ok();
break;
case 2:
result.setMsg("资金账号不存在");
break;
case 3:
result.setMsg("资金不足");
break;
case 4:
result.setMsg("理财产品不存在");
break;
}
}
return result;
}
2.4 测试 143
我们使用swagger
浏览器输入http://localhost:8000/api/doc.html
成功
产品表,剩余可投资金额减100
用户资金表减100
投资记录标增加一条投资记录
3. 更新投资排行榜 144
3.1 定义业务接口 144
UserService
micr-api
//更新投资排行榜 144
User queryById(Integer uid);
3.2 业务接口实现类 144
UserServiceImpl
micr-dataservice
//更新投资排行榜 144
@Override
public User queryById(Integer uid) {
User user = null;
if( uid != null && uid > 0 ){
user = userMapper.selectByPrimaryKey(uid);
}
return user;
}
3.3 消费者controller 144
micr-web
InvestController
/*更新投资排行榜 144*/
private void modifyInvestRank(Integer uid,BigDecimal money){
User user = userService.queryById(uid);
if(user != null){
//更新 redis中的投资排行榜
String key = RedisKey.KEY_INVEST_RANK;
//incrementScore给user.getPhone()对应的值 累加 money.doubleValue()值
stringRedisTemplate.boundZSetOps(key).incrementScore(
user.getPhone(),money.doubleValue());
}
}
3.4 测试 144
一样我们使用swagger测试
浏览器输入http://localhost:8000/api/doc.html
我们投资产品
成功
看看redis中有没有添加数据,成功
4. 前端实现 145
4.1 修改一下代码逻辑 148
这里解释因为我们想要
前端httpRequest.js 148
后端UserController 148
给uid参数加上非必须的参数
解释原因,因为前端想要在非登录状态可以查看产品详情,但是我们在展示产品详情页面需要展示用户资金余额,这个资金余额目前只有用户中心的方法返回了如下图引用了/v1/user/usercenter此后端用户中心的接口,但是后端在接收uid参数时是从请求头中获取的,而我们此时处于非登录状态浏览器中缓存中没有token和用户信息,所以后端就拿不到参数,前端程序就会被错误拦截,为了避免这种情况,我们需要将后端的uid设置为非必须,这样前端就能成功访问了,uid为空后端只会返回空数据,而前端还做了非登录看不到数据的保护,至此没问题
4.2 ProductDetail.vue
<template>
<div>
<Header></Header>
<div class="content clearfix">
<div class="detail-left">
<div class="detail-left-title">{{ product.productName }}({{ product.productNo }}期)</div>
<ul class="detail-left-number">
<li>
<span>历史年化收益率</span>
<p><b>{{ product.rate }}</b>%</p>
<span>历史年化收益率</span>
</li>
<li>
<span>募集金额(元)</span>
<p><b>{{ product.productMoney }}</b>元</p>
<span v-if="product.leftProductMoney > 0 ">募集中 剩余募集金额{{ product.leftProductMoney }}元</span>
<span v-else>已满标</span>
</li>
<li>
<span>投资周期</span>
<p v-if="product.productType == 0 "><b>{{product.cycle}}</b>天</p>
<p v-else><b>{{product.cycle}}</b>个月</p>
</li>
</ul>
<div class="detail-left-way">
<span>收益获取方式</span>
<span>收益返还:<i>到期还本付息</i></span>
</div>
<!--投资记录-->
<div class="datail-record">
<h2 class="datail-record-title">投资记录</h2>
<div class="datail-record-list">
<table align="center" width="880" border="0" cellspacing="0" cellpadding="0">
<colgroup>
<col style="width: 72px" />
<col style="width: 203px" />
<col style="width: 251px" />
<col style="width: 354px" />
</colgroup>
<thead class="datail_thead">
<tr>
<th>序号</th>
<th>投资人</th>
<th>投资金额(元)</th>
<th>投资时间</th>
</tr>
</thead>
<tbody>
<tr v-for="(bid,ind) in bidList" :key="bid.id">
<td>{{ind+1}}</td>
<td class="datail-record-phone">{{ bid.phone }}</td>
<td>{{ bid.bidMoney }}</td>
<td>{{ bid.bidTime }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!--右侧-->
<div class="detail-right">
<div class="detail-right-title">立即投资</div>
<div class="detail-right-mode">
<h3 class="detail-right-mode-title">收益方式</h3>
<p class="detail-right-mode-p"><span>到期还本付息</span></p>
<h3 class="detail-right-mode-title">我的账户可用</h3>
<div class="detail-right-mode-rmb" v-if="logined==false">
<p>资金(元):******</p>
<a href="javascript:void(0);" @click="goLink('/page/user/login',)">请登录</a>
</div>
<div class="detail-right-mode-rmb" v-else>
<p>资金(元):{{this.accountMoney}}</p>
</div>
<h3 class="detail-right-mode-title">利息收入(元){{income}}</h3>
<form action="" id="number_submit">
<p>请在下方输入投资金额</p>
<input type="text" placeholder="请输入日投资金额,应为100元整倍数" v-model="investMoney" @blur="checkInvestMoney" class="number-money" >
<div class="err">{{investMoneyErr}}</div>
<input type="button" value="立即投资" @click="investProduct" class="submit-btn">
</form>
</div>
</div>
</div>
<Footer></Footer>
</div>
</template>
<script>
import Header from "@/components/common/Header";
import Footer from "@/components/common/Footer";
import {doGet, doPost} from "@/api/httpRequest";
import layx from "vue-layx";
export default {
name: "ProductDetail",
components:{
// eslint-disable-next-line vue/no-unused-components
Header,
// eslint-disable-next-line vue/no-unused-components
Footer
},
data(){
return {
product:{
id: 0,
productName: "",
rate: 0.0,
cycle: 0,
releaseTime: 0,
productType: 0,
productNo: "",
productMoney: 0,
leftProductMoney: 0,
bidMinLimit: 0,
bidMaxLimit: 0,
productStatus: 0,
productFullTime: "",
productDesc: ""
},
bidList:[
{
id: 0,
phone: "",
bidTime: "",
bidMoney: 0.00
}],
logined:false,
accountMoney:0.0,
investMoney: 100,
investMoneyErr:'',
income:""
}
},
mounted() {
//判断是否登录 145
if( window.localStorage.getItem("userinfo")){
this.logined = true;
}
this.initPage();
},
methods:{
goLink(url,params){
//使用router做页面跳转, vue中的对象
this.$router.push({
path: url,
query: params
})
},
initPage(){
//查询产品信息
let productId = this.$route.query.productId;
doGet('/v1/product/info',{productId:productId})
.then(resp=>{
if( resp ) {
this.product = resp.data.data;
this.bidList = resp.data.list;
}
})
//查询用户资金 145
doGet('/v1/user/usercenter').then(resp=>{
if( resp && resp.data.code == 1000){
this.accountMoney = resp.data.data.money;
}
})
},
checkInvestMoney() { //146
if (isNaN(this.investMoney)) {
this.investMoneyErr = '请输入正确的金额';
} else if (parseInt(this.investMoney) < 100) {
this.investMoneyErr = '投资最小是100元';
} else if (parseFloat(this.investMoney) % 100 != 0) {
this.investMoneyErr = '投资金额是100的整数倍';
} else {
this.investMoneyErr = '';
//计算利息 利息 = 本金 * 周期 * 利率 147
//当前产品数据 利率是 年利率是 ,百分数 。
//不同类型的产品 ,周期不同, 新手宝是 天, 其他是月(30)
//日利率
let dayRate = this.product.rate / 365 / 100;
//利息
let incomeMoney = 0.0;
if (this.product.productType == 0) { //新手宝
incomeMoney = this.investMoney * this.product.cycle * dayRate;
} else {//其他
incomeMoney = this.investMoney * (this.product.cycle * 30) * dayRate;
}
this.income = incomeMoney.toFixed(2);
}
},
investProduct() {//立即投资 148
//登录, 实名认证过
let userinfo = JSON.parse(window.localStorage.getItem("userinfo"));
if (userinfo) {
//检查是否有实名认证
if (userinfo.name != '') {
//投资
this.checkInvestMoney();
if (this.investMoneyErr == '') {
doPost('/v1/invest/product', {productId: this.product.id, money: this.investMoney})
.then(resp => {
if (resp && resp.data.code == 1000) {
//投资成功,刷新页面 149
this.initPage();
}
})
}
} else {
//进行实名认证
layx.msg('投资前需要实名认证.', {dialogIcon: 'warn', position: 'ct'});
}
} else {
//去登录
layx.msg('请先登录.', {dialogIcon: 'warn', position: 'ct'});
}
}
}
}
</script>
<style scoped>
.err {
color: red;
font-size: 18px;
}
</style>
4.3 测试,投资成功 149
看看数据库
投资记录标增加了四条数据
资金表减少400
产品表 满月宝剩余资金减少400
投资排行榜,在redis中,13812345699机主投资增加400
5. 修改实名认证的bug,自己实现
realNameView.vue
//这里我们修改一个bug,自己实现,因为投资需要根据缓存中的name判断是否实名认证
// ,这里我们要在实名成功后在缓存中加上name
// 从浏览器缓存中获取userinfo的值
const userinfo = JSON.parse(window.localStorage.getItem('userinfo'));
// 给userinfo的name属性赋值
userinfo.name = this.name;
// 将更新后的userinfo重新存回浏览器缓存
window.localStorage.setItem('userinfo', JSON.stringify(userinfo));
标签:product,uid,money,立即,userinfo,&&,投资,productId
From: https://blog.51cto.com/u_15784725/7243697