最近在跟着某视频教程学MyBatis,视频中用到了@SelectKey 这个注解,作用是返回自增的主键

@SelectKey(keyColumn="id",keyProperty="id",resultType=long.class,before=false,statement="select last_insert_id()")
public long insert(OrderInfo orderInfo);

调用的代码如下

long orderId = orderDao.insert(orderInfo);

其中获取的返回值orderID始终为1,然而视频作者并没有发现这个问题,通过梳理程序逻辑,确认逻辑没错,应该返回的ID数据库的自增ID
于是翻查了官方文档 http://www.mybatis.org/mybatis-3/zh/java-api.html#sqlSessions
根据官方文档的解释,这个keyProperty的意思其实就是传进来的Domain对象的对应属性,例如我这里的例子就是OrderInfo对象中名为id的属性
所以问题是在这里,所以正确的用法应该是从orderInfo中去取keyProperty定义的属性的值,例如我这里的是id,那就是这个id属性:

orderDao.insert(orderInfo);
long orderId = orderInfo.getId() ;

那返回值1又是什么意思?这里通过调试来看:
以我现在这个代码为例,当我调用orderDao.insert(orderInfo);方法的时候,MyBatis通过动态代理机制,调用了:

org.apache.ibatis.binding.MapperProxy.invoke(Object, Method, Object[])

这个方法的源码为:

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

根据代码逻辑,我的method.getDeclaringClass()是一个Interface的DAO,所以最终执行的是最后的org.apache.ibatis.binding.MapperMethod.execute(SqlSession, Object[])
这个方法的代码如下:

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

从上面的代码上看,最终return的返回时其实是rowCountResult,也就是这条sql最终影响的行数
为了验证这个问题,写一个测试用例:

userDao.insertTest(OrderInfo orderInfo)方法如下,其中insert语句一次性插入了3行数据,返回值存入到orderInfo的id属性中:

@Insert("INSERT INTO test(b,c) VALUES(1,1),(2,2),(3,3)")
@SelectKey(keyColumn="id",keyProperty="id",resultType=long.class,before=false,statement="select last_insert_id()")
public long insertTest(OrderInfo orderInfo);

同时我们取出orderDao.insertTest的返回值:

long ret = orderDao.insertTest(orderInfo);
System.out.println("=====>\nret:"+ret);

同时为了让结果更加直观,我把test表的自增量设置为从4开始,a为自增字段

最终测试结果如下,返回值为3,最终自增ID为7,验证了之前的猜想,直接从dao的方法中取出来的只是操作的行数,最终的自增ID应该是通过你传入的对象,并且在keyProperty定义的属性名中取出来。

本文原则上适用于大多数Netgear路由器因为刷了第三方固件导致刷不回原厂固件的情况。

手头上有一台以前买的Netgear  WNDR3700 V4路由,以前为了功能刷了石像鬼,今天准备卖出去了,买家说要简单易用稳定的固件,所以打算刷回原厂固件。但是刷回原厂固件后,发现路由一直在重启,没办法进入原厂固件系统。查询资料得知,DD-WRT会修改固件的原有分区,导致和原来分区不一致所致。

那问题就好解决了:

1、准备好固件系统
1.1、刷支持SSH的第三方固件,我这里还是用石像鬼,这里只需要gargoyle-ar71xx-nand-wndr3700v4-ubi-factory.img就够了:https://github.com/gygy/gygy.github.io/tree/master/%E5%9B%BA%E4%BB%B6%E6%9B%B4%E6%96%B0/%E7%9F%B3%E5%83%8F%E9%AC%BC%E5%8F%8C%E9%9D%A2%E6%9D%BF%E5%9B%BA%E4%BB%B6%E6%9B%B4%E6%96%B0-update2018/gargoyle-1.10.x-V0.0.3
1.2、WNDR3700 V4原厂固件:http://support.netgear.cn/doucument/detail.asp?id=2203
2、刷第三方固件并打开SSH功能,然后用SSH工具登陆上去。
先cat /proc/mtd 看看当前的固件所在存储分区,如图所示,我这里是firmware,名字还有可能是linux之类的:

3、格式化系统分区并重启:
mtd erase firmware
reboot
4、这个时候系统会自动重启并进入TFTP,按照自己喜欢的方法刷入原厂固件即可。

本文针对nginx做的一些设置,其他服务器请自行搜索配置
1、因为每次更新WordPress的时候都会增加install.php文件,所以需要禁止install.php的访问

location = /wp-admin/install.php {
        return 301 https://blog.haiwaidao.com;
}

2、禁止针对wp-login.php的爆破,一个是被爆破成功后的危险,第二个是不断有post进来对php的消耗还是不小的,我曾经被爆破到无法访问,通过nginx层面禁止是非常有效的手段。因为我这边遇到进行爆破的主要都是来自欧洲各国的IP,所以我这里禁止了所有非中文用户访问本页面

location = /wp-login.php {
  if ( $http_accept_language !~* zh){
    return 503;
  }
  fastcgi_pass unix:/tmp/php-cgi.sock;
  include fastcgi.conf;
}

设置好后可以用curl测试效果

curl -I -H "Accept-Language:en" https://blog.haiwaidao.com/wp-login.php

3、针对2的补充,如果你的攻击来源主要是国内或者攻击者伪造浏览器语言可以增加鉴权
3.1、利用利用htpasswd创建鉴权文件

htpasswd -c /home/nginx/users.db username
#如果系统没有htpasswd 可以使用这个python版的https://gist.github.com/parhammmm/3775241
python htpasswd.py -b -c /home/nginx/users.db username password

3.2、配置nginx

location = /wp-login.php {
        auth_basic "Login";
        auth_basic_user_file /home/nginx/users.db;
        #下面这个是配置解析php文件,否则会变成下载php文件
        fastcgi_pass unix:/tmp/php-cgi.sock;
        #fastcgi_index index.php;
        include fastcgi.conf;
}

4、禁用xmlrpc.php访问,大多数人用不到这个,但是这个却可以被用来破解密码也可以被用来发动DDOS,曾经我也被用来破解导致宕机了,频率极高

location = /xmlrpc.php {
        return 404;
}

关于Chrome OS就不多说了,Google的操作系统,这里给大家操作一波装Chrome OS的方法,非常简单,简单过装Windows。
注意事项:装Chrome OS的时候会清空硬盘所有数据,如有数据要保留,一定要另外备份好。
一、确定要使用的版本
Chrome OS是一份开源的系统,所以除了Google官方,还有其他第三方开发的各种版本,例如:

  1. 1、Chromium OS,Chrome OS的原始开发版,所有特性都是在Chromium OS上先使用
  2. 2、CloudReady,这是Neverware基于Chromium OS开发的一个大众版OS,应该算是目前用得比较多的第三方Chrome OS了把
  3. 3、Flint OS,一个基于Chromium OS开发的国产Chrome OS,据说内置有SS客户端。
  4. 4、自己编译,可以参考此文:https://www.librehat.com/compile-chromium-os-yourself/

二、制作U盘启动盘

  1. 1、准备好一个16G以上的U盘,并确保自己备份好了里面的数据,因为等下会清空里面的数据
  2. 2、其实无论选择哪个版本,基本操作都是一样的,我这里选择了Chromium OS,可以自己编译或者是用别人编译好的,我这里用https://chromium.arnoldthebat.co.uk/?dir=./ 编译好的版本并下载,然后解压。
  3. 3、如果你有Chrome浏览器,直接用Google自己的Chromebook Recovery Utility ,或者其他类似的Win32 Disk Imager。
  4. 4、这里我用Google自己的工具,按照下图选择步骤2里面解压的文件,
     
  5. 5、按提示制作好后,插入你的笔记本或者电脑,并使用U盘启动。

三、安装Chromium OS
进入U盘系统后就是Chromium OS,如果此事你的设备不能翻墙,可以参考这里 ,登陆好账号后,按CTRL+ALT+T打开crosh,输入 install /dev/sda 即可开始安装,注意,安装过程中会自动重建分区表,并且整个安装过程无需人工干预。另外如果安装过程中需要你登陆,请参考你下载的镜像相关信息,而我这里下载的这个版本默认账号为chronos,密码为空。
安装完后会提示可以重启并拔下U盘了,到此Chromium OS安装完成,非常简单。

四、安装Chrome OS
如果你想要稳定的Chrome OS,那么你在 上面的基础上再参考此文:https://pjw.io/article/2013/12/22/install-chromium-os-and-update-to-chrome-os-on-pc/

本文方法同样适用于原来开通了余利宝,却突然消失,无法在支付宝找到余利宝入口的情况。
今年9月初的时候,我就在自己支付宝看到了余利宝的选项,并且这个之前支付宝已经限制了余额宝的总额不能超过10W。为了让老婆能存多点钱,本来想给老婆也开通余利宝的,但是却发现老婆的支付宝账户只有余额宝没有余利宝。网上查了很多文章,只有一个方法,就是在支付宝首页顶端搜索余利宝,然后打开余利宝应用。然而这个方法在这里行不通。最后经过不断尝试终于发现了方法,需要一个能在支付宝看到余利宝的账户。


方法一:

在能看到余利宝的账户中,点击余利宝,然后马上点击右上角的三个点(速度要快,因为一下子就会 变成客服助手),这个时候如果点击成功会出现一个分享页面,分享到你的支付宝账号,就可以打开余利宝应用,往里面转1元,就可以开通余利宝了


方法二:

如果你没有这样的朋友或者操作不来,直接把下面这个链接复制到支付宝聊天对话框,然后打开即可打开余利宝应用:


方法三:

支付宝首页顶端搜索“商家服务”,然后打开名为“商家服务”得应用,在里面找到余利宝并点开