转账案例介绍MD5加密和ThreadLocal的使用

介绍

  • 通过这个例子,分别介绍ThreadLocalMD5加密的使用。
    • 使用ThreadLocal:线程局部变量:作用实现把数据绑定到线程中,从而实现线程安全。
    • 使用MessageDigest:类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。

问题和解决

  • 当转账时遇到的问题(其他小问题这里不展示):
    • (1)转账时,一个账户减钱成功,但是这是出现异常,导致另一账户没有加上钱。
    • (2)表单重复提交
  • 解决方法:
    • (1)开启事务,但是事务也有一个问题。
      • 问题:就是开启的事务连接数据库连接不是同一个连接。数据库连接是从连接池中拿的,事务连接不是从连接池中拿的,这样就造成开启的事务无效。事务也可以从连接池中拿,但是这两个永远也拿不到同一个连接
      • 解决:使用ThreadLocal线程局部变量
    • (2)重复提交表单两种解决方式:
      • 利用Session防止表单重复提交(推荐),保存一个标识(令牌),令牌一样时可提交,不一样时不可提交。使用输入框(hidden)把令牌隐藏掉,使客户看不到
      • 通过JavaScript屏蔽提交按钮(不推荐),但是js容易被破坏,不安全。

粘贴部分代码(全部的代码太多了)

threadLocal线程绑定

  • 把对象绑定线程上,线程不变对象不变
  • 这种方式调用事务就是使用的一个连接
  • 这里使用Druid数据连接池
  • DataSourceUtils工具类代码示例:(省略部分代码)
//创建ThreadLocal线程
private static ThreadLocal<Connection> threadLocal;
static{
  threadLocal = new ThreadLocal<>();
}
//创建连接,绑定对象
public static Connection getConnection(){
  Connection connection = threadLocal.get();
  if(connection==null) {
      try {
          connection = dataSource.getConnection();
       } catch (SQLException e) {
           e.printStackTrace();
       }
       threadLocal.set(connection);
   }
   return connection;
}

//启动事务
public static void beginTranscation(){
   Connection connection = getConnection();
   try {
       connection.setAutoCommit(false);
   } catch (SQLException e) {
       e.printStackTrace();
   }

}
//提交
public static void commit(){
   Connection connection = getConnection();
   try {
       connection.commit();
   } catch (SQLException e) {
       e.printStackTrace();
   }
}
//回滚
public static void rollBack(){
   Connection connection = getConnection();
   try {
      connection.rollback();
   } catch (SQLException e) {
       e.printStackTrace();
   }
}
//关闭
public static void close(){
   Connection connection = getConnection();
   try {
       connection.close();
       //关闭后,移除对象
       threadLocal.remove();
   } catch (SQLException e) {
       e.printStackTrace();
   }
}
  • 代码示例:
//存钱
@Override
public void save(Integer id, BigDecimal money) {
   QueryRunner qr = new QueryRunner();
   try {
       //获取连接(必须在该方法中获取连接),执行SQL语句
       qr.update(DataSourceUtil.getConnection(),"update account set money=money+? where id=?",money,id);
   } catch (SQLException e) {
       e.printStackTrace();
   }
}
//取钱
@Override
public void take(Integer id, BigDecimal money) {
   QueryRunner qr = new QueryRunner();
   try {
       //获取连接(必须在该方法中获取连接),执行SQL语句
       qr.update(DataSourceUtil.getConnection(),"update account set money=money-? where id=?",money,id);
   } catch (SQLException e) {
       e.printStackTrace();
   }
}
  • Servlet中代码:
try {
  //开始事务
  DataSourceUtil.beginTranscation();
  as.transfer(Integer.parseInt(id1),Integer.parseInt(id2),new BigDecimal(account1));
  //提交事务
  DataSourceUtil.commit();
  response.getWriter().write("转账成功");
} catch (Exception e) {
  e.printStackTrace();
  //事务回滚
  DataSourceUtil.rollBack();
  response.getWriter().write("转账失败,错误信息:"+e.getMessage());
}finally {
  //关闭事务
  DataSourceUtil.close();
}

使用MD5加密生成令牌

  • 简要说明:
    • 当可客户端请求服务器时,创建一个指令,发送给客户端并保存。
    • 在发送给客户端后,服务器立即移除Session中的指令。
    • 客户端每次请求都会比较指令是否一致,一致可以提交,不一致不可提价
    • 这样就可以防止表单重复提交
  • 代码示例:
public class TokenProccessor {
   //创建单例,饿汉式
   public TokenProccessor() {
   }
   private static final TokenProccessor instance = new TokenProccessor();
   public static TokenProccessor getInstance(){
       return instance;
   }
   //加密,生成令牌
   public String makeToken(){
       SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
       String str = sdf.format(new Date()) + new Random().nextInt(999999999);
       //md5加密
       try {
           //使用md5消息摘要
           MessageDigest md = MessageDigest.getInstance("MD5");
           //更新数据
           md.update(str.getBytes());
           //加密,返回加密之后的结果
           byte[] digest = md.digest();
           //编码,转换成string类型
           return Base64.getEncoder().encodeToString(digest);
       } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
       }
       return null;
   }
}
  • 创建一个生成指令的工具类:
  • 代码示例:
public class TokenTools {
   //生成指令,并保存到Session中
   public static void createToken(HttpServletRequest request,String tokenkey){
       String token = TokenProccessor.getInstance().makeToken();
       request.getSession().setAttribute(tokenkey,token);
   }
   //移除Session中的指令
   public static void removeToken(HttpServletRequest request,String tokenkey){
       request.getSession().removeAttribute(tokenkey);
   }
}
  • Servlet中代码:
//获取客户端的令牌
String client_token = request.getParameter("tokenkey");
//获取服务端的令牌
String server_token = (String) request.getSession().getAttribute("tokenkey");
if(client_token==null||client_token.trim().length()==0){
   response.getWriter().write("不可重复提交");
   return;
}
if(!client_token.equals(server_token)){
   response.getWriter().write("不可重复提交");
   return;
}
//调用移除session中的指令
TokenTools.removeToken(request,"tokenkey");
  • HTML页面中的代码:
<!--客户端保存指令-->
<input type="hidden" value="${tokenkey}" name="tokenkey"/>


转载请注明:Memory的博客 » 点击阅读原文

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦