【分布式金融交易模型】账户开立

账户开立

1. 前置知识

1.1 MP的使用

1.1.1 引入依赖

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.1</version>
    </dependency>

1.1.2 修改配置文件

在这里插入图片描述

1.1.3 创建实体类

创建包entity,编写实体类AccAgrtTableEntity.java,并使用lombok简化实体类的编写:
在这里插入图片描述

package com.icbc.distributed.accopen.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;
import lombok.Data;

/**
 * 
 * 
 * @author ${author}
 * @email ${email}
 * @date 2022-07-20 20:37:46
 */
@Data
@TableName("acc_agrt_table")//实现实体类型和数据库中的表实现映射,如AccAgrtTableEntity实体类会映射到数据库acc_agrt_table表
public class AccAgrtTableEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 行号
	 * 指定实体类属性和表中的主键字段对应。
	 * MP默认认为id字段是主键列,其他名字的属性MP无法默认自动填充,所以这里想以snId为主键,则需要加@TableId注解
	 */
	@TableId
	private Long snId;
	/**
	 * 账号
	 */

	private String  accId;
	/**
	 * 币种
	 */
	private Integer currType;
	/**
	 * 利率变动方式;1-固定利率;2-变动利率
	 */
	private Integer rateIncm;
	/**
	 * 利率变动周期;1-按日变动;2-按月变动;3-按季变动;4-按半年变动;5-按年变动;
	 */
	private Integer rateAltf;
	/**
	 * 基准利率;有符号数
	 */
	private Integer baseRate;
	/**
	 * 浮动方式;0-不浮动;2-浮动点数;3-浮动比例;
	 */
	private Integer floaType;
	/**
	 * 浮动率有符号数
	 */
	private Integer floaRate;
	/**
	 * 最后计息日期
	 */
	private Date lastRateDate;
	/**
	 * 最后修改机构
	 */
	private String lastOrganno;
	/**
	 * 最后修改柜员
	 */
	private String lastTellerno;
	/**
	 * 最后修改日期
	 */
	private Date lastModifyDate;
	/**
	 * 地区编号
	 */
	private Integer regionId;

}

1.1.4 Mapper的CRUD

1.1.4.1 创建mapper/dao接口

创建包mapper/dao,编写Mapper接口:AccAgrtTableDao.java
在这里插入图片描述

@Mapper
public interface AccAgrtTableDao extends BaseMapper<AccAgrtTableEntity> {
	
}
1.1.4.2 启动类添加注解

在Spring Boot启动类中添加@MapperScan注解,扫描Dao文件夹:
在这里插入图片描述

@MapperScan("com.icbc.distributed.accopen.dao")
@EnableDiscoveryClient
@SpringBootApplication()
public class DistributedAccopenApplication {

    public static void main(String[] args) {
        SpringApplication.run(DistributedAccopenApplication.class, args);
    }

}
1.1.4.3 Mapper的通用CRUD测试

实体类:

package com.atguigu.mybatisplus.entity;

@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

mapper接口:

package com.atguigu.mybatisplus.mapper;

public interface UserMapper extends BaseMapper<User> {
    
}

MP中的基本CRUD在内置的BaseMapper中都已得到了实现。
创建MapperTests测试类:

package com.atguigu.mybatisplus;

@SpringBootTest
public class MapperTests {

    @Resource
    private UserMapper userMapper;
}
1.1.4.3.1 增
@Test
public void testInsert(){

    User user = new User();
    user.setName("Helen");
    user.setAge(18);
    //不设置email属性,则生成的动态sql中不包括email字段

    int result = userMapper.insert(user);
    System.out.println("影响的行数:" + result); //影响的行数
    System.out.println("id:" + user.getId()); //id自动回填
}
1.1.4.3.2 删
@Test
public void testDelete(){

    int result = userMapper.deleteById(5);
    System.out.println("影响的行数:" + result);
}
1.1.4.3.3 改
@Test
public void testUpdate(){

    User user = new User();
    user.setId(1L);
    user.setAge(28);

    //注意:update时生成的sql自动是动态sql
    int result = userMapper.updateById(user);
    System.out.println("影响的行数:" + result);
}
1.1.4.3.4 查
@Test
public void testSelect(){

    //按id查询
    User user = userMapper.selectById(1);
    System.out.println(user);

    //按id列表查询
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    users.forEach(System.out::println);

    //按条件查询
    Map<String, Object> map = new HashMap<>();
    map.put("name", "Helen"); //注意此处是表中的列名,不是类中的属性名
    map.put("age", 18);
    List<User> users1 = userMapper.selectByMap(map);
    users1.forEach(System.out::println);
}
1.1.4.3.5 根据前端表单提交过来的accid去数据库查询acc_id为accid的账号

在这里插入图片描述

1.1.4.4 Mapper的自定义CRUD测试

如果MP的BaseMapper内置的CRUD不满足业务需求,需要自定义CRUD时,可以参考Mybatis的做法:写DAO接口,然后在dao.xml中写具体的SQL语句。
在这里插入图片描述
在这里插入图片描述

1.1.4.5 条件构造器QueryWrapper实现更高级的查询

在MP中我们可以使用通用Mapper(BaseMapper)实现基本查询,也可以使用自定义Mapper(自定义XML)来实现更高级的查询。当然你也可以结合条件构造器来方便的实现更多的高级查询。
在这里插入图片描述
new QueryWrapper<AccInfoTableEntity>().eq("acc_id",accid):将AccInfoTableEntity封转成QueryWrapper对象封装操作类,然后使用QueryWrapper操作类中的eq方法去数据库表中查询相应数据。
在这里插入图片描述
在这里插入图片描述
AccInfoTableEntity实体类:(数据库表acc_info_table的字段信息)

package com.icbc.distributed.accopen.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;

/**
 * 
 * 
 * @author ${author}
 * @email ${email}
 * @date 2022-07-20 20:37:46
 */
@Data
@TableName("acc_info_table")
public class AccInfoTableEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 行号
	 */
//	@TableId
	private Long snId;
	/**
	 * 账号
	 */
	@TableId(type = IdType.INPUT)
	private String accId;
	/**
	 * 账户状态 0-正常 1-销户
	 */
	private Integer accStatus;
	/**
	 * 账户名称
	 */
	private String accTitle;
	/**
	 * 密码
	 */
	private String password;
	/**
	 * 通存标志
	 */
	private Integer depositSign;
	/**
	 * 通兑标志
	 */
	private Integer exchangeSign;
	/**
	 * 存现开关
	 */
	private Integer depositSwitch;
	/**
	 * 取现开关
	 */
	private Integer withdrawalSwitch;
	/**
	 * 开立机构
	 */
	private String openingInstitution;
	/**
	 * 最后修改日期
	 */
	private Date lastModifiedDate;
	/**
	 * 签订日期
	 */
	private Date signingDate;
	/**
	 * 注销日期
	 */
	private Date logoutDate;
	/**
	 * 地区编号
	 */
	private Integer regionId;
	/**
	 * 支付密码
	 */
	private String payPassword;
}

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取。
queryWrapper.lt()——小于
queryWrapper.le()——小于等于
queryWrapper.gt()——大于
queryWrapper.ge()——大于等于
queryWrapper.eq()——等于
queryWrapper.ne()——不等于
queryWrapper.betweeen(“age”,10,20)——age在值10到20之间
queryWrapper.notBetweeen(“age”,10,20)——age不在值10到20之间
queryWrapper.like(“属性”,“值”)——模糊查询匹配值‘%值%’
queryWrapper.notLike(“属性”,“值”)——模糊查询不匹配值‘%值%’
queryWrapper.likeLeft(“属性”,“值”)——模糊查询匹配最后一位值‘%值’
queryWrapper.likeRight(“属性”,“值”)——模糊查询匹配第一位值‘值%’
queryWrapper.isNull()——值为空或null
queryWrapper.isNotNull()——值不为空或null
queryWrapper.in(“属性”,条件,条件 )——符合多个条件的值
queryWrapper.notIn(“属性”,条件,条件 )——不符合多个条件的值
queryWrapper.or()——或者
queryWrapper.and()——和
queryWrapper.orderByAsc(“属性”)——根据属性升序排序
queryWrapper.orderByDesc(“属性”)——根据属性降序排序
queryWrapper.inSql(“sql语句”)——符合sql语句的值
queryWrapper.notSql(“sql语句”)——不符合SQL语句的值
queryWrapper.esists(“SQL语句”)——查询符合SQL语句的值
queryWrapper.notEsists(“SQL语句”)——查询不符合SQL语句的值

1.1.5 通用Service的CRUD(以转账微服务的AccInfoTableService为例)

MP中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑。

1.1.5.1 创建Service接口

在这里插入图片描述

package com.icbc.distributed.transfer.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.icbc.common.utils.PageUtils;
import com.icbc.distributed.transfer.entity.AccInfoTableEntity;

import java.util.Map;

/**
 * MP中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑
 * AccInfoTableService继承了IService接口,那么就继承了该接口中的方法(泛型为AccInfoTableEntity,即对AccInfoTableEntity实体类进行CRUD)
 */
public interface AccInfoTableService extends IService<AccInfoTableEntity> {
    // 由于IService没有我们想要的分页功能,所以在该接口也自定义一个分页方法queryPage
    PageUtils queryPage(Map<String, Object> params);
}
1.1.5.2 创建Service实现类

创建 impl 包,创建 AccAgrtTableServiceImpl,继承 ServiceImpl,实现 AccAgrtTableService接口:
在这里插入图片描述

package com.icbc.distributed.transfer.service.impl;

import org.springframework.stereotype.Service;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.icbc.common.utils.PageUtils;
import com.icbc.common.utils.Query;

import com.icbc.distributed.transfer.dao.AccInfoTableDao;
import com.icbc.distributed.transfer.entity.AccInfoTableEntity;
import com.icbc.distributed.transfer.service.AccInfoTableService;

// ServiceImpl的泛型是<通用mapper, 通用mapper管理的实体>,即<AccInfoTableDao, AccInfoTableEntity>
// 继承 ServiceImpl,默认情况下拥有ServiceImpl下定义的方法;实现AccInfoTableService,目的是扩展自己的业务逻辑
@Service("accInfoTableService")
public class AccInfoTableServiceImpl extends ServiceImpl<AccInfoTableDao, AccInfoTableEntity> implements AccInfoTableService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<AccInfoTableEntity> page = this.page(
                new Query<AccInfoTableEntity>().getPage(params),
                new QueryWrapper<AccInfoTableEntity>()
        );

        return new PageUtils(page);
    }

}
1.1.5.3 测试
package com.icbc.distributed.transfer.tccservice;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.icbc.common.utils.R;
import com.icbc.distributed.common.IdWorker;
import com.icbc.distributed.transfer.entity.AccInfoTableEntity;
import com.icbc.distributed.transfer.entity.TransferInfoEntity;
import com.icbc.distributed.transfer.service.*;

import io.seata.spring.annotation.GlobalTransactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;


@Service("tccTransaction")
public class TccTransactionService {

    @Autowired
    private AccInfoTableService accInfoTableService;

    @Autowired
    IdWorker idWorker;// 分布式自增长ID
    
    public R checkAcc(TransferInfoEntity transferInfoEntity){

        Map<String, Object> map = new HashMap<>();
        //kay是字段名 value是字段值
        map.put("acc_id", transferInfoEntity.getAccIdFrom());

        // 4查询账户信息表
        // 将数据库中的付方账号和前端表单输入的用户信息进行核对
        // 判断借方账号是否存在
        List<AccInfoTableEntity> accInfoTableEntityFromList = accInfoTableService.listByMap(map);//根据账号id查询数据库acc_info_table表的信息
        if(accInfoTableEntityFromList.size()<1){

            return R.error("账号错误, 账号不存在");
        }

TransferInfoEntity实体类:(前端表单传过来的信息)

package com.icbc.distributed.transfer.entity;


import lombok.Data;
import java.util.Date;

@Data
public class TransferInfoEntity {

    /**
     * 业务唯一编码
     */
    private String txnId;

    /**
     * 执行机构
     */
    private String execOrganno;

    /**
     * 执行柜员
     */
    private String execTellerno;


    /**
     * 币种
     */
    private Integer currType;

    /**
     * 发起 账号
     */
    private String accIdFrom;

    /**
     * 发起 账户名称
     */
    private String accTitleFrom;


    /**
     * 接受  账号
     */
    private String accIdTo;

    /**
     * 接受  账户名称
     */
    private String accTitleTo;

    /**
     * 发生额
     */
    private Double amount;


    /**
     * 交易日期
     */
    private Date execDate;

    /**
     * 支付密码
     * */
    private String password;
}

AccInfoTableEntity实体类:(对应数据库表的字段信息)

package com.icbc.distributed.transfer.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.Date;
import lombok.Data;

/**
 *
 */
@Data
@TableName("acc_info_table")//该实体类对应数据库acc_info_table表
public class AccInfoTableEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 行号
	 */
	private Long snId;
	/**
	 * 账号
	 */
	@TableId(value = "acc_id",type = IdType.INPUT)
	private String accId;
	/**
	 * 账户状态 0-正常 1-销户
	 */
	private Integer accStatus;
	/**
	 * 账户名称
	 */
	private String accTitle;
	/**
	 * 密码
	 */
	private String password;
	/**
	 * 通存标志
	 */
	private Integer depositSign;
	/**
	 * 通兑标志
	 */
	private Integer exchangeSign;
	/**
	 * 存现开关
	 */
	private Integer depositSwitch;
	/**
	 * 取现开关
	 */
	private Integer withdrawalSwitch;
	/**
	 * 开立机构
	 */
	private String openingInstitution;
	/**
	 * 最后修改日期
	 */
	private Date lastModifiedDate;
	/**
	 * 签订日期
	 */
	private Date signingDate;
	/**
	 * 注销日期
	 */
	private Date logoutDate;
	/**
	 * 地区编号
	 */
	private Integer regionId;

	private String payPassword;
}

根据账号id查询数据库acc_info_table表的信息,其中listByMap方法是IService接口自带的方法:
在这里插入图片描述

1.1.6 自定义Service接口(以账户开立微服务的LoginAccService为例)

1.1.6.1 添加Service接口方法

在这里插入图片描述

public interface LoginAccService {
    String loginAcc(AccLoginEntity accLoginEntity);
}

AccLoginEntity实体类:(前端传过来的)
在这里插入图片描述

1.1.6.2 实现Service接口方法

在这里插入图片描述

package com.icbc.distributed.accopen.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.icbc.common.exception.MsgCode.BaseCode;
import com.icbc.common.exception.RRException;
import com.icbc.distributed.accopen.dao.AccInfoTableDao;
import com.icbc.distributed.accopen.entity.AccInfoTableEntity;
import com.icbc.distributed.accopen.service.DetectLoginService;
import com.icbc.distributed.accopen.service.LoginAccService;
import com.icbc.distributed.accopen.vo.request.AccLoginEntity;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class LoginAccServiceImpl implements LoginAccService {
    @Autowired
    AccInfoTableDao accInfoTableDao;
    @Autowired
    DetectLoginService detectLoginService;
    @Override
    public String loginAcc(AccLoginEntity accLoginEntity){

        String accid=accLoginEntity.getAccid();
        // 加密密码
        String password= DigestUtils.md5Hex(accLoginEntity.getPassword() + accid);
        //1.根据前端表单提交过来的accid去数据库查询acc_id为accid的账号是否存在,不存在则无法登录(判断用户是否存在)
        AccInfoTableEntity entity=accInfoTableDao.selectOne(new QueryWrapper<AccInfoTableEntity>()
                .eq("acc_id",accid));
        if (entity==null){
            //没有该账号
//            throw new RRException(BaseCode.LOGIN_ACCID_INVAILD_EXCEPTION);
            return "账号不存在";
        } else {

            //2. 账号正确,开始判断账号登录次数
            if(!detectLoginService.detectLoginOk(accid)){
                return "账号已被锁定,请24小时后重新尝试";
            }

//            detectLoginService.registLogin( accid, password);
            // 3. 根据前端表单提交过来的accid和password去数据库查询acc_id为accid、password为password的账号是否存在,若不存在,则说明前端表单输入的password不正确(登录密码是否正确)
            entity=accInfoTableDao.selectOne(new QueryWrapper<AccInfoTableEntity>()
                    .eq("acc_id",accid)
                    .eq("password",password));
            if(entity==null){
                //密码错误
                //登录失败记录
                int fail = detectLoginService.registLoginError(accid);
                return "登录失败,登录失败将锁定账号24小时,剩余"+String.valueOf(fail)+"次。";
//                throw new RRException(BaseCode.LOGIN_PASSWORD_INVAILD_EXCEPTION);
            }else{
                //成功登录记
                detectLoginService.registLoginSucces(accid);
                return "登录成功";
            }

        }
    }
}
1.1.6.3 测试

在这里插入图片描述

package com.icbc.distributed.accopen.controller;

import com.icbc.common.exception.MsgCode.BaseCode;
import com.icbc.common.utils.R;
import com.icbc.distributed.accopen.entity.AccInfoTableEntity;
import com.icbc.distributed.accopen.jwt.JwtUtil;
import com.icbc.distributed.accopen.service.LoginAccService;
import com.icbc.distributed.accopen.vo.request.AccLoginEntity;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

//@RefreshScope
@RestController
@RequestMapping("/AccLogin")
public class AccLoginController {
    @Autowired
    LoginAccService loginAccService;

    @Autowired
    JwtUtil jwtUtil;

    @RequestMapping("/login")
    public R login(@RequestBody AccLoginEntity accLoginEntity){
        //loginAccService.loginAcc(accLoginEntity);
        String jwt=jwtUtil.createJwt(accLoginEntity.getAccid());
        String ret = loginAccService.loginAcc(accLoginEntity);
        Map map = new HashMap<String,String>();
        if(ret.equals("登录成功")){
            map.put("token",jwt);
        //map.put("claims",claims);
            map.put("code",10009);
            return R.ok(map);}
        else {
            map.put("ret",ret);
            map.put("code",10008);
            map.put("msg","error");
            return R.error(map);}
    }
}

1.2 JWT权限控制

1.2.1 单点登录

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2.2 JWT令牌(用户信息存储在JWT令牌中)

在这里插入图片描述
JWT令牌的组成:
在这里插入图片描述
该对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。

每一个子串表示了一个功能块,总共有以下三个部分:JWT头、有效载荷和签名。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2.3 JWT的用法

客户端接收服务器返回的JWT,将其存储在CookielocalStorage(本地存储)中。

此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。

当跨域时,也可以将JWT放置于POST请求的数据主体中。

1.2.3.1 基本依赖
<dependencies>
    <!-- JWT -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.7.0</version>
    </dependency>

    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

</dependencies>
1.2.3.2 Jwts.builder()生成token
package com.atguigu.jwt;

public class JwtTests {

    //过期时间,毫秒,24小时
    private static long tokenExpiration = 24*60*60*1000;
    //秘钥
    private static String tokenSignKey = "atguigu123";

    @Test
    public void testCreateToken(){
        
        String token = Jwts.builder()
            	// 1.JWT头
                .setHeaderParam("typ", "JWT") //令牌类型
                .setHeaderParam("alg", "HS256") //签名算法
				// 2.有效载荷
                .setSubject("guli-user") //令牌主题
                .setIssuer("atguigu")//签发者
                .setAudience("atguigu")//接收者
                .setIssuedAt(new Date())//签发时间
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) //过期时间
                .setNotBefore(new Date(System.currentTimeMillis() + 20*1000)) //20秒后可用
                .setId(UUID.randomUUID().toString())

                .claim("nickname", "Helen")
                .claim("avatar", "1.jpg")
				// 3.签名哈希
                .signWith(SignatureAlgorithm.HS256, tokenSignKey)//签名哈希
                .compact(); //转换成字符串

        System.out.println(token);
    }
}

结果:
在这里插入图片描述

1.2.3.3 Jwts.parser()解析token

假设要解析这个token
在这里插入图片描述

@Test
public void testGetUserInfo(){

    String token = "jwt字符串";
    // Jwts.parser()方法解析token
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey)
        								 .parseClaimsJws(token);

    Claims claims = claimsJws.getBody();

    String subject = claims.getSubject();
    String issuer = claims.getIssuer();
    String audience = claims.getAudience();
    Date issuedAt = claims.getIssuedAt();
    Date expiration = claims.getExpiration();
    Date notBefore = claims.getNotBefore();
    String id = claims.getId();

    System.out.println(subject);
    System.out.println(issuer);
    System.out.println(audience);
    System.out.println(issuedAt);
    System.out.println(expiration);
    System.out.println(notBefore);
    System.out.println(id);;

    String nickname = (String)claims.get("nickname");
    String avatar = (String)claims.get("avatar");

    System.out.println(nickname);
    System.out.println(avatar);
}

在这里插入图片描述

1.2.4 JWT问题和趋势

  1. JWT默认不加密,但可以加密。生成原始令牌后,可以使用该令牌再次对其进行加密。
  2. 当JWT未加密时,一些私密数据无法通过JWT传输。
  3. JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。
  4. JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦JWT签发,在有效期内将会一直有效。
  5. JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行身份验证。
  6. 为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。

1.2.5 不在生成token的时候设置token的过期时间,而是将token存入redis中,以此解决token过期时间问题

1.2.5 JWT跨域的问题

2. 用户登录

在输入系统的网址后,便可以进入项目的主界面,也就是系统的登录界面。
在这里插入图片描述
在这里插入图片描述
用户登录时,请求不经过拦截器直接到达controlleraccId和密码被前端捕获后进行加密操作。随后传入数据层进行数据库查询。

若查询失败,则返回前端失败信息;
若查询成功,则生成token,将accId和过期时间放入token并用预先给定的密钥进行签发并返回给前端。
在这里插入图片描述
在这里插入图片描述

        String jwt=jwtUtil.createJwt(accLoginEntity.getAccid());

在这里插入图片描述

2.1 JWT做用户认证

2.1.1 微服务模块中添加依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>

2.1.2 JWT工具(添加JwtUtils类)

package com.icbc.distributed.accopen.jwt;


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtUtil {

    @Value("wzt")
    private String key;

    @Value("3600")
    private long ttl;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public long getTtl() {
        return ttl;
    }

    public void setTtl(long ttl) {
        this.ttl = ttl;
    }
    // 创建Jwt
    public String createJwt(String id){
        //设置过期时间
        ttl=6000000;

        long nowm=System.currentTimeMillis();
        Date now=new Date(nowm);

        /** 设置JWT所存储的信息
         * signWith:签名哈希
         * setId:jwt的唯一身份标识,主要用来作为一次性token(通过uuid随机生成id),从而避免重放攻击
         SignatureAlgorithm.HS256:签名方法 HS256
         base64EncodedSecretKey:秘钥
         */
        JwtBuilder builder= Jwts.builder().setId(id).signWith(SignatureAlgorithm.HS256,"wzt->123");

        // 设置JWT令牌的过期时间
        if(ttl>0){
            builder.setExpiration(new Date(nowm+ttl));
        }
        return builder.compact();// 连接成字符串
    }
    /**
     * 解析Jwt字符串
     *
     * @param jwtStr Jwt字符串
     * @return Claims 解析后的对象
     */
    public Claims parseJWT(String jwtStr){
        return Jwts.parser()
                .setSigningKey("wzt->123")
                .parseClaimsJws(jwtStr)
                .getBody();
    }
}

在这里插入图片描述

2.2 使用redis缓存登录失败次数(5分钟内一次性登录失败3次,账号会被锁定1天)

RedisTemplate
在Spring Boot项目中中,默认集成Spring Data Redis,Spring Data Redis针对Redis提供了非常方便的操作模版RedisTemplate,并且可以进行连接池自动管理。

2.2.1 引入Redis

2.2.1.1 项目中集成Redis
<!-- spring boot redis缓存引入 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 缓存连接池-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- redis 存储 json序列化 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
2.2.1.2 添加Redis连接配置

application.yml中添加如下配置:

#spring: 
  redis:
    host: 192.168.100.100 #设置访问的redis数据库的ip地址
    port: 6379 #redis安装时设置的默认端口
    database: 0 #使用redis中的第1个库
    password: 123456 #默认为空
    timeout: 5000ms #最大等待时间,超时则抛出异常,否则请求一直等待
    lettuce:
      pool:
        max-active: 20  #最大连接数,负值表示没有限制,默认8
        max-wait: -1    #最大阻塞等待时间,负值表示没限制,默认-1
        max-idle: 8     #最大空闲连接,默认8
        min-idle: 0     #最小空闲连接,默认0
2.2.1.3 启动Redis服务

远程连接Linux服务器:

#启动服务
cd /usr/local/redis-5.0.7
bin/redis-server redis.conf

2.2.2 编写配置类,构造RedisTemplate(使用字符串存储key,使用JSON序列化存储value)

RedisTemplate是用来访问redis数据的模板类。

因为RedisTemplate是<Object,Object>类型的,不利于我们操作,所以要写一个配置类。
在这里插入图片描述
在这里插入图片描述

2.2.3 使用通过RedisTemplate访问redis,缓存登录失败次数数据、获取数据

每登录失败一次,redis就相应缓存该登录记录。
在这里插入图片描述
怎么缓存登录失败次数到redis?

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class RedisTests {

    //我们需要通过RedisTemplate访问redis,故注入RedisTemplate
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    //访问redis数据库中value为字符串的数据
    public void testStrings() {

        String redisKey = "test:count";//声明key

        redisTemplate.opsForValue().set(redisKey, 1);//存数据,给redisKey这个key赋值为1

        System.out.p

3. 校验用户登录

使用拦截器对请求进行拦截,然后使用JWT验证机制对请求头的token进行校验
在这里插入图片描述
拦截器配置为拦截除登录注册页面外的所有页面。当前端访问api接口时(如转账、账户信息查询等),JWT验证机制可以对接口进行一定的保护。

如果携带的token经过解析出来的accId与请求参数中的不一致,说明此用户并未进行登录,此时不能进行后续的操作,需要返回给前端权限不足错误。
在这里插入图片描述

package com.icbc.distributed.accopen.filter;


import com.icbc.distributed.accopen.jwt.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
*  访问接口时验证动作
*/
@Component
public class JwtFilter extends HandlerInterceptorAdapter {
    @Autowired
    private JwtUtil jwtUtil;
    // 1. 根据规则,非登录页面的http请求被拦截器拦截
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        System.out.println("经过了拦截器");
        // 这里只是获取前端传过来的token?并没有校验token(得使用checkToken方法?)?
        // 答:这里校验了token了!jwtUtil.parseJWT(token)相当于调用了checkToken方法。

        // 2. 拦截器根据密钥对token进行解析,将解析结果放置在请求头内
        // 3. 业务模块将token中携带的accId从请求头中取出

        // 前端将token放到请求头的,故使用request.getHeader("Authorization")才能获得token,"Authorization"是该token的key
        final String token = request.getHeader("Authorization");
        if (token!=null) {
            
            // 4. 校验了token是否合法(该语句运行通过,则合法)
            Claims claims = jwtUtil.parseJWT(token);
            // 5. 将解析结果claims放置在请求头内
            request.setAttribute("claims", claims);
        }
        return true;
    }
}

在这里插入图片描述

    /**
     * 解析Jwt字符串
     *
     * @param jwtStr Jwt字符串
     * @return Claims 解析后的对象
     */
    public Claims parseJWT(String jwtStr){
        return Jwts.parser()
                .setSigningKey("wzt->123")
                .parseClaimsJws(jwtStr)
                .getBody();
    }

4. 用户注册与账户开立

点击主界面的开户按钮,便可以进入系统的开户主界面,在主界面中需要输入地区、姓名、手机号、身份证号、登录密码、交易密码等信息。
在这里插入图片描述

4.1 账户注册和开户是一起的

账户号为accId,密码为DigestUtils.md5Hex(accOpenEntity.getPassword() + accId)
在这里插入图片描述
在这里插入图片描述
注册流程:

  1. 首先通过uuid工具类生成业务唯一编号,如果重复则循环进行直到得到不重复的uuid
  2. 调用函数将地区名称转为地区编号,网点名称转为网点编号。
  3. 调用 accId 生成函数(generateTableIdgenerateAccId方法),根据地区、网点和顺序号计算校验位并生成账号。
  4. 将生成的账号在数据库中进行查重。
  5. 更新数据库。
    在这里插入图片描述
    其中,对于3,是这样生成账号ID的:

生成tableId:
在这里插入图片描述
在这里插入图片描述
生成accId:地区+网点+数据库顺序号
在这里插入图片描述
而对于5,需要更新6个数据库表:
在这里插入图片描述
数据库更新流程:

  1. 新增账户信息表,设置账号、密码、开立机构和开立日期等字段;
  2. 新增业务登记簿,将业务唯一编号、交易日期、操作机构、操作柜员等字段存入;
  3. 新增账户余额表,添加新开账户的余额和币种信息;
  4. 新增账户明细表,存入开户过程的操作日志,存入账号、业务唯一编号、币种、交易日期、交易代码等字段。
  5. 新增存款协议表。
    private void updateDateBase(AccOpenEntity accOpenEntity, AccIdTableEntity accIdTableEntity, Date tradeDate, String accId,Long uuid)  {

        //table2用户信息表
        AccInfoTableEntity accInfoTableEntity = new AccInfoTableEntity();
        accInfoTableEntity.setAccId(accId);
        accInfoTableEntity.setAccStatus(1);
        accInfoTableEntity.setAccTitle(accOpenEntity.getAccTitle());
        accInfoTableEntity.setPassword(DigestUtils.md5Hex(accOpenEntity.getPassword() + accId));
        accInfoTableEntity.setDepositSign(1);
        accInfoTableEntity.setExchangeSign(1);
        accInfoTableEntity.setDepositSwitch(1);
        accInfoTableEntity.setWithdrawalSwitch(1);
        accInfoTableEntity.setOpeningInstitution(accOpenEntity.getExecOrganno());
        accInfoTableEntity.setLastModifiedDate(tradeDate);
        accInfoTableEntity.setSigningDate(tradeDate);
        accInfoTableEntity.setPayPassword(DigestUtils.md5Hex(accOpenEntity.getPayPassword()+ accId));
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd");
        try{
            accInfoTableEntity.setLogoutDate(simpleDateFormat.parse("9999-12-30"));
        }catch (ParseException ex){
            ex.printStackTrace();
            log.error(ex.getMessage());
        }
        //地区号
        accInfoTableEntity.setRegionId(accIdTableEntity.getRegionId());
        accInfoTableDao.insert(accInfoTableEntity);

    //table3业务登记簿
        BizRegTableEntity bizRegTableEntity=new BizRegTableEntity();
        bizRegTableEntity.setTxnDate(tradeDate);
        bizRegTableEntity.setTxnId(String.valueOf(uuid));
        bizRegTableEntity.setChannelType(1);
        bizRegTableEntity.setExecOrganno(accOpenEntity.getExecOrganno()); //操作机构
        bizRegTableEntity.setExecTellerno(accOpenEntity.getExecTellerno());
        bizRegTableEntity.setTxnCode(1);
        bizRegTableEntity.setTxnType(2);
        bizRegTableEntity.setCashTransferFlag(1);
        bizRegTableEntity.setDebitAccId("null");
        bizRegTableEntity.setDebitAccName("null");
        bizRegTableEntity.setCreditCurrType(0);
        bizRegTableEntity.setCreditAmount(0l);
        bizRegTableEntity.setStatus(0);
        bizRegTableEntity.setLastModifyDate(tradeDate);
        bizRegTableEntity.setRegionId(accIdTableEntity.getRegionId());//地区号
        bizRegTableEntity.setDebitCurrType(0);
        bizRegTableEntity.setDebitAmount(0l);
        bizRegTableEntity.setCreditAccId("null");
        bizRegTableEntity.setCreditAccName("null");
        bizRegTableDao.insert(bizRegTableEntity);


        //table4新增账户余额
        AccBalanceTableEntity accBalanceTableEntity =new AccBalanceTableEntity();
        accBalanceTableEntity.setAccId(accId);
        accBalanceTableEntity.setCurrType(0);
        accBalanceTableEntity.setCurBalance(0l);
        accBalanceTableEntity.setYdayBalance(0l);
        accBalanceTableEntity.setLastTxnDate(tradeDate);
        accBalanceTableEntity.setRegionId(accIdTableEntity.getRegionId());//调用公共层方法地区转地区号
        accBalanceTableDao.insert(accBalanceTableEntity);



        //table5新增账户明细表
        AccDetailsTableEntity accDetailsTableEntity=new AccDetailsTableEntity();
        accDetailsTableEntity.setDepartId(1);//分区编号
        accDetailsTableEntity.setAccId(accId);
        accDetailsTableEntity.setCurrType(0);
        accDetailsTableEntity.setTxnId(String.valueOf(uuid));
        accDetailsTableEntity.setExecOrganno(accOpenEntity.getExecOrganno()); //操作机构 ,调用公共层方法操作机构转操作机构号
        accDetailsTableEntity.setExecTellerno(accOpenEntity.getExecTellerno());
        accDetailsTableEntity.setExecDate(tradeDate);
        accDetailsTableEntity.setTxnCode(0);
        accDetailsTableEntity.setCashFlag(1);
        accDetailsTableEntity.setLoanFlag(0);
        accDetailsTableEntity.setAmount(0l);
        accDetailsTableEntity.setBalance(0l);
        accDetailsTableEntity.setBalance(0l);
        accDetailsTableEntity.setRegionId(accIdTableEntity.getRegionId());//地区号
        accDetailsTableDao.insert(accDetailsTableEntity);

        //table6新增存款协议表
        AccAgrtTableEntity accAgrtTableEntity=new AccAgrtTableEntity();
        accAgrtTableEntity.setAccId(accId);
        accAgrtTableEntity.setCurrType(0);
        accAgrtTableEntity.setRateIncm(1);
        accAgrtTableEntity.setRateAltf(5);
        accAgrtTableEntity.setBaseRate(0);
        accAgrtTableEntity.setFloaType(0);
        accAgrtTableEntity.setFloaRate(0);
        accAgrtTableEntity.setLastRateDate(tradeDate);
        accAgrtTableEntity.setLastOrganno(accOpenEntity.getExecOrganno()); //操作机构 ,调用公共层方法操作机构转操作机构号
        accAgrtTableEntity.setLastTellerno(String.valueOf(accOpenEntity.getExecTellerno()));
        accAgrtTableEntity.setLastModifyDate(tradeDate);
        accAgrtTableEntity.setRegionId(accIdTableEntity.getRegionId());//地区号
        accAgrtTableDao.insert(accAgrtTableEntity);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cashapxxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值