核心业务1:账户绑定业务
核心业务1:账户绑定业务
1.业务流程图
2.账户绑定数据库设计
3.账户绑定业务流程
4.代码逻辑
5.代码逻辑细节
核心业务1:账户绑定业务
1.业务流程图
①用户绑定数据到商户平台(已登录用户)
-
A 用户在绑定页输入信息
-
B单击开户绑定数据到尚融宝
-
C并且尚融宝提交表单数据给汇付宝
②用户绑定数据到汇付宝
- D用户再跳转汇付宝绑定页(根据表单数据展示绑定页)
- E单击确认绑定成功汇付宝
- F汇付宝异步调用尚融宝同步数据
- G汇付宝同步返回结果页面
2.账户绑定数据库设计
①尚融宝数据库表
- 包含全局信息的user_info
在F操作中更新表
- 包含绑定信息的user_bind
在B操作中更新表
在F操作中更新表
②汇付宝数据库表
- user_bind表
在E操作中更新表
- user_account表
在E操作中更新表
③尚融宝汇付宝数据库表
- user_id
所有的数据表都有唯一user_id - bind_code和status
在汇付宝中生成后异步更新尚融宝的user_bind和user_info
3.账户绑定业务流程
①前端
- 必须登录才可以进入bind页面
发送到后端时携带token(登录后会保存在浏览器中,根据请求拦截器自动携带token),token中携带数据全局user_id - 进入bind绑定页面输入信息后点击确认
信息更新到尚融宝user_bind中,同时尚融宝返回自动提交表单数据直接提交到汇付宝 - 跳转到汇付宝提交页面
汇付宝根据提交表单信息回显表单页,点击提交后进入汇付宝返回的结果页
②尚融宝
- 接受bind绑定页传入的表单
对token进行校验,生成汇付宝需要的表单信息返回前端并且设置前端自动提交 - 接受尚融宝的异步调用
验证签名并且将尚融宝的user_info表和user_bind表与汇付宝中的表绑定(通过bind_code属性)
③汇付宝
- 接受尚融宝提交的页面回显给用户提交页
- 接受用户提交的数据更新汇付宝user_account和user_bind表
- 异步返回给尚融宝绑定数据
- 同步返回给用户结果页
4.代码逻辑
①前端
- 绑定页
完成调用后端/api/core/userBind/auth/bind接口的调用,自动提交接口返回的结果表单
<template><div class="personal-main"><div class="personal-pay"><h3><i>开通第三方账户</i></h3><div class="pay-notice"><p>请开通汇付宝存管账户以便于您正常理财</p></div><div class="pay-form"><ul><li><label>真实姓名</label><inputv-model="userBind.name"type="text"class="pay-txt"maxlength="16"placeholder="您的真实姓名"/></li><li><label>身份证号</label><inputv-model="userBind.idCard"type="text"class="pay-txt"maxlength="18"placeholder="您的身份证号"/><div id="idCardErrorDiv"><p style="margin-top:10px;">身份证信息认证后将不可修改,请您仔细填写</p></div></li><li><label>绑定银行</label><inputv-model="userBind.bankType"type="text"class="pay-txt"placeholder="银行名称"/></li><li><label>银行卡号</label><inputv-model="userBind.bankNo"type="text"class="pay-txt"placeholder="本人持有的银行卡"/></li><li><label>预留手机</label><inputv-model="userBind.mobile"type="text"class="pay-txt"placeholder="银行卡预留手机号"/></li><li><label> </label><input v-model="agree" type="checkbox" />我已阅读并同意<a href="#" class="c-orange" target="_blank">《汇付宝托管账户协议》</a></li><li><label> </label><el-button :disabled="!agree" @click="commitBind()" type="primary">开户</el-button></li></ul></div></div></div>
</template>
<script>
export default {data() {return {agree: false,userBind: {},}},methods: {commitBind() {this.$alert('<div style="size: 18px;color: red;">您即将前往汇付宝绑定账号</div>','前往汇付宝资金托管平台',{dangerouslyUseHTMLString: true,confirmButtonText: '立即前往',callback: (action) => {//TODO 提交用户信息console.log(action)if (action == 'confirm') {this.$axios.$post('/api/core/userBind/auth/bind', this.userBind).then((response) => {document.write(response.data.formStr)})}},})},},
}
</script>
②尚融宝
- controller
完成前端接口/api/core/userBind/auth/bind的调用和汇付宝异步调用/api/core/userBind/auth/bind/notify
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.UserBindVO;
import com.atguigu.srb.core.service.UserBindService;
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;/* <p>* 用户绑定表 前端控制器* </p> @author Likejin* @since 2023-04-09*/
@RestController
@Api(tags = "会员账号绑定")
@RequestMapping("/api/core/userBind")
@Slf4j
public class UserBindController {@Resourceprivate UserBindService userBindService;//需要登录的才能访问的接口用/auth来定义@ApiOperation("账户绑定提交数据")@PostMapping("/auth/bind")public R bind(@RequestBody UserBindVO userBindVO, HttpServletRequest request){//从header中拿到token,确保用户已经登录,从token中获取到userId(token中有id和username)String token = request.getHeader("token");//已经对token进行校验了Long userId = JwtUtils.getUserId(token);//根据userId做账户绑定,生成一个动态表单的字符串String formStr = userBindService.commitBindUser(userBindVO,userId);return R.ok().data("formStr",formStr);}@ApiOperation("账户绑定异步回调")//汇付宝以post发起请求@PostMapping("/notify")//返回值时String,返回为sucess则成功回调,汇付宝不会重试public String notify(HttpServletRequest request) {//解析汇付宝发过来的request为mapMap<String, Object> paramMap = RequestHelper.switchMap(request.getParameterMap());log.info("账户绑定异步回调接受参数{}", JSON.toJSONString(paramMap));//校验签名(约定相同的算法,计算用其他参数得到的签名和实际签名是否相同)if(!RequestHelper.isSignEquals(paramMap)){log.error("用户账号绑定异步回调签名验证错误:"+ JSON.toJSONString(paramMap));return "fail";}log.info("验签成功!开始账户绑定");userBindService.notify(paramMap);return "success";}}
- service
package com.atguigu.srb.core.service;import com.atguigu.srb.core.pojo.entity.UserBind;
import com.atguigu.srb.core.pojo.vo.UserBindVO;
import com.baomidou.mybatisplus.extension.service.IService;import java.util.Map;/* <p>* 用户绑定表 服务类* </p> @author Likejin* @since 2023-04-09*/
public interface UserBindService extends IService<UserBind> {String commitBindUser(UserBindVO userBindVO, Long userId);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.UserBindEnum;
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.UserBindMapper;
import com.atguigu.srb.core.mapper.UserInfoMapper;
import com.atguigu.srb.core.pojo.entity.UserBind;
import com.atguigu.srb.core.pojo.entity.UserInfo;
import com.atguigu.srb.core.pojo.vo.UserBindVO;
import com.atguigu.srb.core.service.UserBindService;
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.HashMap;
import java.util.Map;/* <p>* 用户绑定表 服务实现类* </p> @author Likejin* @since 2023-04-09*/
@Service
public class UserBindServiceImpl extends ServiceImpl<UserBindMapper, UserBind> implements UserBindService {@Resourceprivate UserInfoMapper userInfoMapper;@Overridepublic String commitBindUser(UserBindVO userBindVO, Long userId) {//不同的userid,相同的身份证存在则不允许QueryWrapper<UserBind> userBindQueryWrapper = new QueryWrapper<>();userBindQueryWrapper.eq("id_card",userBindVO.getIdCard()).ne("user_id",userId);UserBind userBind = baseMapper.selectOne(userBindQueryWrapper);Assert.isNull(userBind, ResponseEnum.USER_BIND_IDCARD_EXIST_ERROR);//检查用户是否曾经填写过绑定表单userBindQueryWrapper = new QueryWrapper<>();userBindQueryWrapper.eq("user_id",userId);userBind = baseMapper.selectOne(userBindQueryWrapper);//如果userbind不存在则创建记录if(userBind ==null){//创建用户绑定记录userBind = new UserBind();//把前端传入的数据与数据库user_bind绑定BeanUtils.copyProperties(userBindVO,userBind);userBind.setUserId(userId);userBind.setStatus(UserBindEnum.NO_BIND.getStatus());baseMapper.insert(userBind);}else{//相同的user_id,存在,则更新BeanUtils.copyProperties(userBindVO,userBind);baseMapper.updateById(userBind);}//动态生成表单//组装表单的参数HashMap<String, Object> paramMap = new HashMap<>();//商户唯一标识paramMap.put("agentId", HfbConst.AGENT_ID);//前端提交的表单对象paramMap.put("agentUserId", userId);paramMap.put("idCard",userBindVO.getIdCard());paramMap.put("personalName", userBindVO.getName());paramMap.put("bankType", userBindVO.getBankType());paramMap.put("bankNo", userBindVO.getBankNo());paramMap.put("mobile", userBindVO.getMobile());//返回结果给平台前端的结果页paramMap.put("returnUrl", HfbConst.USERBIND_RETURN_URL);//返回商户平台的后端的接口paramMap.put("notifyUrl", HfbConst.USERBIND_NOTIFY_URL);//提交时间戳(远程调用)paramMap.put("timestamp", RequestHelper.getTimestamp());//生成签名,有已知的Sign_key(两者商量好的)paramMap.put("sign", RequestHelper.getSign(paramMap));//生成动态表单字符串(辅助生成字符串)String formStr = FormHelper.buildForm(HfbConst.USERBIND_URL,paramMap);//前端得到一个字符串,直接提交给汇付宝绑定用户return formStr;}/* @param paramMap:* @return void* @author Likejin* @description 处理汇付宝回调参数* @date 2023/4/14 21:28*/@Transactional(rollbackFor = Exception.class)@Overridepublic void notify(Map<String, Object> paramMap) {String bindCode = (String)paramMap.get("bindCode");String agentUserId = (String)paramMap.get("agentUserId");//根据userid查询userBind对象QueryWrapper<UserBind> userBindQueryWrapper = new QueryWrapper<>();userBindQueryWrapper.eq("user_id",agentUserId);UserBind userBind = baseMapper.selectOne(userBindQueryWrapper);//更新尚融宝数据用户绑定表 user_binduserBind.setBindCode(bindCode);userBind.setStatus(UserBindEnum.BIND_OK.getStatus());baseMapper.updateById(userBind);//更新尚融宝数据用户信息表 user_info//原来这些值都没传递,都传到了汇付宝,需要用这些值更新用户信息表 user_infoUserInfo userInfo = userInfoMapper.selectById(agentUserId);userInfo.setBindCode(bindCode);//名字原来和手机号绑定userInfo.setName(userBind.getName());userInfo.setIdCard(userBind.getIdCard());userInfo.setBindStatus(UserBindEnum.BIND_OK.getStatus());userInfoMapper.updateById(userInfo);}
}
③汇付宝
- 表单提交数据
- 异步请求数据
- 异步接受返回结果类型
String (success,其他)
5.代码逻辑细节
①引入工具类的作用
- JwtUtils
前端发送的请求用于校验token - BeanUtils
复制对象相同的属性给另一个对象 - 枚举类
字符串类型的签名
汇付宝异步回调和同步回调地址,提交给汇付宝表单的地址 - RequestHelper
解析汇付宝发来的request为map
校验汇付宝带的签名是否正确
②业务逻辑细节
- 前端发送数据给尚融宝时
尚融宝需要先检验token是否正确
尚融宝需要确认身份证数据唯一:如果数据库中已有身份证,则判断id是否相同,不相同则返回身份证只能注册一个账号。
尚融宝需要确认如果id存在则进行更新操作,不存在则进行插入操作
尚融宝组成动态表单时,需要携带returnUrl,notifyUrl,表单提交的地址,sign令牌 - 汇付宝异步调用尚融宝时
尚融宝验证签名是否正确
尚融宝更新自己的user_info和user_bind
其中user_bind只需要更新(bind_code和status)
其中user_info由于之前未更新,此处更新多数据包括bind_code和status
返回success字符串