1. 页面原型   138

立即投资_xml

立即投资_xml_02

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唯一索引

立即投资_sql_03

立即投资_xml_04

<!--购买理财产品之查询用户账户金额 给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

立即投资_sql_05

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

立即投资_xml_06

成功

立即投资_sql_07

产品表,剩余可投资金额减100

立即投资_xml_08

用户资金表减100

立即投资_sql_09

投资记录标增加一条投资记录

立即投资_User_10

3. 更新投资排行榜  144

立即投资_xml_11

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

我们投资产品

立即投资_User_12

成功

立即投资_xml_13

看看redis中有没有添加数据,成功

立即投资_xml_14

4. 前端实现  145

4.1 修改一下代码逻辑   148

这里解释因为我们想要

前端httpRequest.js   148

立即投资_sql_15

后端UserController  148

给uid参数加上非必须的参数

解释原因,因为前端想要在非登录状态可以查看产品详情,但是我们在展示产品详情页面需要展示用户资金余额,这个资金余额目前只有用户中心的方法返回了如下图引用了/v1/user/usercenter此后端用户中心的接口,但是后端在接收uid参数时是从请求头中获取的,而我们此时处于非登录状态浏览器中缓存中没有token和用户信息,所以后端就拿不到参数,前端程序就会被错误拦截,为了避免这种情况,我们需要将后端的uid设置为非必须,这样前端就能成功访问了,uid为空后端只会返回空数据,而前端还做了非登录看不到数据的保护,至此没问题

立即投资_xml_16


立即投资_xml_17

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

立即投资_sql_18

看看数据库

投资记录标增加了四条数据

立即投资_xml_19

资金表减少400

立即投资_sql_20

产品表 满月宝剩余资金减少400

立即投资_xml_21

投资排行榜,在redis中,13812345699机主投资增加400

立即投资_xml_22

5. 修改实名认证的bug,自己实现

realNameView.vue

立即投资_sql_23

//这里我们修改一个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));