首页
关于
留言
Search
1
红米 AX3000 (AX6) 路由器解锁 SSH 教程
6,676 阅读
2
网盘挂载程序sharelist美化教程
4,216 阅读
3
小米路由器 AX3600 开启SSH教程,官方固件即可安装 ShellClash开启科学上网
2,168 阅读
4
Oracle 甲骨文 ARM VPS 自动抢购脚本
1,819 阅读
5
编译带PassWall和SSR-plus插件的Openwrt系统
1,393 阅读
前端
Vue
React
后端
Java
Python
PHP
数据库
运维
杂谈
小程序
影视资源
登录
Search
标签搜索
Java
Linux
Mysql
IDEA
Debian
Docker
Springboot
CentOS
Cloudflare
Maven
JavaScript
SQL
Wordpress
宝塔
Nginx
Windows
MacBook
JS
CSS
Openwrt
William
累计撰写
144
篇文章
累计收到
702
条评论
首页
栏目
前端
Vue
React
后端
Java
Python
PHP
数据库
运维
杂谈
小程序
影视资源
页面
关于
留言
搜索到
144
篇与
的结果
2022-07-10
Eclipse 快捷键大全(建议收藏)
编辑类快捷键 Ctrl+1快速修复(最经典的快捷键,可以解决很多问题,比如 import 类、try catch 包围等)Ctrl+Shift+F格式化当前代码Ctrl+Shift+M添加类的 import 导入Ctrl+Shift+O组织类的 import 导入(既有 Ctrl+Shift+M 的作用,又可以去除没用的导入,一般用这个导入包)Ctrl+Y重做(与撤销 Ctrl+Z 相反)Alt+/内容辅助(用户编辑的好帮手,省了很多次键盘敲打,太常用了)Ctrl+D删除当前行或者多行(不用为删除一行而按那么多次的删除键)Alt+↓当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了)Alt+↑当前行和上面一行交互位置(同上)Ctrl+Alt+↓复制当前行到下一行Ctrl+Alt+↑复制当前行到上一行Shift+Enter在当前行的下一行插入空行(这时鼠标可以在当前行的任一位置,不一定是最后)Ctrl+/注释当前行,再按则取消注释选择类快捷键 Alt+Shift+↑选择封装元素Alt+Shift+←选择上一个元素Alt+Shift+→选择下一个元素Shift+←从光标处开始往左选择字符Shift+→从光标处开始往右选择字符Ctrl+Shift+←选中光标左边的单词Ctrl+Shift+→选中光标右边的单词移动类快捷键 Ctrl+←光标移到左边单词的开头,相当于 vim 的 bCtrl+→光标移到右边单词的末尾,相当于 vim 的 e搜索类快捷键 Ctrl+K参照选中的 Word 快速定位到下一个(如果没有选中 word,则搜索上一次使用搜索的 word)Ctrl+Shift+K参照选中的 Word 快速定位到上一个Ctrl+J正向增量查找(按下 Ctrl+J 后,你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在状态栏中显示没有找到了,查一个单词时,特别实用,要退出这个模式,按 escape 键)Ctrl+Shift+J反向增量查找(和上条相同,只不过是从后往前查)Ctrl+Shift+U列出所有包含字符串的行Ctrl+H打开搜索对话框Ctrl+G工作区中的声明Ctrl+Shift+G工作区中的引用导航类快捷键 Ctrl+Shift+T搜索类(包括工程和关联的第三 jar 包)Ctrl+Shift+R搜索工程中的文件Ctrl+E快速显示当前 Editer 的下拉列表(如果当前页面没有显示的用黑体表示)F4打开类型层次结构F3跳转到声明处Alt+←前一个编辑的页面Alt+→下一个编辑的页面(当然是针对上面那条来说了)Ctrl+PageUp/PageDown在编辑器中,切换已经打开的文件调试类快捷键 F5单步跳入F6单步跳过F7单步返回F8继续Ctrl+Shift+D显示变量的值Ctrl+Shift+B在当前行设置或者去掉断点Ctrl+R运行至行(超好用,可以节省好多的断点)重构(一般重构的快捷键都是 Alt+Shift 开头的)类快捷键 Alt+Shift+R重命名方法名、属性或者变量名 (尤其是变量和类的 Rename,比手工方法能节省很多劳动力)Alt+Shift+M把一段函数内的代码抽取成方法 (这是重构里面最常用的方法之一了,尤其是对一大堆泥团代码有用)Alt+Shift+C修改函数结构(比较实用,有 N 个函数调用了这个方法,修改一次搞定)Alt+Shift+L抽取本地变量(可以直接把一些魔法数字和字符串抽取成一个变量,尤其是多处调用的时候)Alt+Shift+F把 Class 中的 local 变量变为 field 变量 (比较实用的功能)Alt+Shift+I合并变量Alt+Shift+V移动函数和变量(不常用)Alt+Shift+Z撤销(重构的后悔药)其他快捷键 Alt+Enter显示当前选择资源的属性,windows 下的查看文件的属性就是这个快捷键,通常用来查看文件在 windows 中的实际路径Ctrl+↑文本编辑器 上滚行Ctrl+↓文本编辑器 下滚行Ctrl+M最大化当前的 Edit 或 View (再按则反之)Ctrl+O显示类中方法和属性的大纲,能快速定位类的方法和属性(在查找 Bug 时非常有用)Ctrl+T快速显示当前类的继承结构Ctrl+W关闭当前 Editer(windows 下关闭打开的对话框也是这个,还有 qq、旺旺、浏览器等都是)Ctrl+L文本编辑器 转至行F2显示工具提示描述
2022年07月10日
13 阅读
0 评论
0 点赞
2022-07-10
Spring Boot 接入支付宝,手把手带你实战!
支付宝推出了新的转账接口 alipay.fund.trans.uni.transfer(升级后安全性更高,功能更加强大) ,老转账接口 alipay.fund.trans.toaccount.transfer 将不再维护,新老接口的一个区别就是新接口采用的证书验签方式。1、将支付宝开放平台里下载的3个证书放在 resources 下面2、写支付宝支付的配置文件 alipay.propertiesalipay.appId=你的应用id alipay.serverUrl=https://openapi.alipay.com/gateway.do alipay.privateKey=你的应用私钥 alipay.format=json alipay.charset=UTF-8 alipay.signType=RSA2 alipay.appCertPath=/cert/appCertPublicKey_2021001164652941.crt alipay.alipayCertPath=/cert/alipayCertPublicKey_RSA2.crt alipay.alipayRootCertPath=/cert/alipayRootCert.crt3、引入pom依赖<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.10.97.ALL</version> </dependency>4、将配置信息注入AliPayBeanimport lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Component @PropertySource("classpath:/production/alipay.properties") @ConfigurationProperties(prefix = "alipay") @Data public class AliPayBean { private String appId; private String privateKey; private String publicKey; private String serverUrl; private String domain; private String format; private String charset; private String signType; private String appCertPath; private String alipayCertPath; private String alipayRootCertPath; }5、写配置类import com.alipay.api.AlipayClient; import com.alipay.api.CertAlipayRequest; import com.alipay.api.DefaultAlipayClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.FileCopyUtils; import java.io.InputStream; @Configuration public class AliConfig { @Value("${custom.http.proxyHost}") private String proxyHost; @Value("${custom.http.proxyPort}") private int proxyPort; @Value("${spring.profiles.active}") private String activeEnv; @Autowired private AliPayBean aliPayBean; @Bean(name = {"alipayClient"}) public AlipayClient alipayClientService() throws Exception{ CertAlipayRequest certAlipayRequest = new CertAlipayRequest(); //设置网关地址 certAlipayRequest.setServerUrl(aliPayBean.getServerUrl()); //设置应用Id certAlipayRequest.setAppId(aliPayBean.getAppId()); //设置应用私钥 certAlipayRequest.setPrivateKey(aliPayBean.getPrivateKey()); //设置请求格式,固定值json certAlipayRequest.setFormat(aliPayBean.getFormat()); //设置字符集 certAlipayRequest.setCharset(aliPayBean.getCharset()); //设置签名类型 certAlipayRequest.setSignType(aliPayBean.getSignType()); //如果是生产环境或者预演环境,则使用代理模式 if ("prod".equals(activeEnv) || "stage".equals(activeEnv) || "test".equals(activeEnv)) { //设置应用公钥证书路径 certAlipayRequest.setCertContent(getCertContentByPath(aliPayBean.getAppCertPath())); //设置支付宝公钥证书路径 certAlipayRequest.setAlipayPublicCertContent(getCertContentByPath(aliPayBean.getAlipayCertPath())); //设置支付宝根证书路径 certAlipayRequest.setRootCertContent(getCertContentByPath(aliPayBean.getAlipayRootCertPath())); certAlipayRequest.setProxyHost(proxyHost); certAlipayRequest.setProxyPort(proxyPort); }else { //local String serverPath = this.getClass().getResource("/").getPath(); //设置应用公钥证书路径 certAlipayRequest.setCertPath(serverPath+aliPayBean.getAppCertPath()); //设置支付宝公钥证书路径 certAlipayRequest.setAlipayPublicCertPath(serverPath+aliPayBean.getAlipayCertPath()); //设置支付宝根证书路径 certAlipayRequest.setRootCertPath(serverPath+aliPayBean.getAlipayRootCertPath()); } return new DefaultAlipayClient(certAlipayRequest); } public String getCertContentByPath(String name){ InputStream inputStream = null; String content = null; try{ inputStream = this.getClass().getClassLoader().getResourceAsStream(name); content = new String(FileCopyUtils.copyToByteArray(inputStream)); }catch (Exception e){ e.printStackTrace(); } return content; } }6、写支付工具类import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.domain.AlipayTradeAppPayModel; import com.alipay.api.domain.AlipayTradeQueryModel; import com.alipay.api.request.AlipayTradeAppPayRequest; import com.alipay.api.request.AlipayTradeQueryRequest; import com.alipay.api.response.AlipayTradeAppPayResponse; import com.alipay.api.response.AlipayTradeQueryResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; /** * @description:支付宝工具类 * @Date:2022-07-10 */ @Slf4j @Service public class AliPayUtils { @Autowired @Qualifier("alipayClient") private AlipayClient alipayClient; /** * 交易查询接口 * @param request * @return * @throws Exception */ public boolean isTradeQuery(AlipayTradeQueryModel model) throws AlipayApiException { AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); request.setBizModel(model); AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.certificateExecute(request); if(alipayTradeQueryResponse.isSuccess()){ return true; } else { return false; } } /** * app支付 * @param model * @param notifyUrl * @return * @throws AlipayApiException */ public String startAppPay(AlipayTradeAppPayModel model, String notifyUrl) throws AlipayApiException { AlipayTradeAppPayRequest aliPayRequest = new AlipayTradeAppPayRequest(); model.setProductCode("QUICK_MSECURITY_PAY"); aliPayRequest.setNotifyUrl(notifyUrl); aliPayRequest.setBizModel(model); // 这里和普通的接口调用不同,使用的是sdkExecute AlipayTradeAppPayResponse aliResponse = alipayClient.sdkExecute(aliPayRequest); return aliResponse.getBody(); } /** * 转账接口 * * @param transferParams * @return AlipayFundTransToaccountTransferResponse */ public AlipayFundTransUniTransferResponse doTransferNew(TransferParams transferParams) throws Exception { String title = (StringUtils.isNotBlank(transferParams.getRemark()) ? transferParams .getRemark() : "转账"); //转账请求入参 AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); //转账参数 BizContentForUniTransfer bizContent = new BizContentForUniTransfer(); bizContent.setOut_biz_no(transferParams.getOutBizNo()); bizContent.setTrans_amount(MathUtil.changeF2Y(Math.abs(Integer.parseInt(transferParams.getAmount())))); bizContent.setProduct_code("TRANS_ACCOUNT_NO_PWD"); bizContent.setBiz_scene("DIRECT_TRANSFER"); bizContent.setOrder_title(title); Participant participant = new Participant(); participant.setIdentity(transferParams.getPayeeAccount()); participant.setIdentity_type(transferParams.getPayeeType()); participant.setName((StringUtils.isNotBlank(transferParams.getPayeeRealName()) ? transferParams .getPayeeRealName() : StringUtils.EMPTY)); bizContent.setPayee_info(participant); bizContent.setRemark(title); request.setBizContent(JSON.toJSONString(bizContent)); //转账请求返回 AlipayFundTransUniTransferResponse response = null; try { response = alipayClient.certificateExecute(request); } catch (Exception e) { log.info("doTransfer exception,异常信息:{}", e.toString()); log.info("doTransfer exception,支付宝返回信息:{}", JSONObject.toJSONString(response)); } log.info("doTransfer,AlipayFundTransUniTransferResponse:{}", JSONObject.toJSONString(response)); return response; } }Tips:转账用到的类@Data public class TransferParams { /** * 应用编号 */ private Long appId; /** * 创建人id */ private Long createdBy; /** * 转账业务订单号 */ private String outBizNo; /** * 收款方识别方式 */ private String payeeType; /** * 收款方账号,可以是支付宝userId或者支付宝loginId */ private String payeeAccount; /** * 转账金额,单位分 */ private String amount; /** * 付款方名称 */ private String payerShowName; /** * 收款方名称 */ private String payeeRealName; /** * 备注 */ private String remark; /** * 支付宝转账流水号 */ private String orderId; }import lombok.Data; import java.math.BigDecimal; /** * 支付宝转账参数 */ @Data public class BizContentForUniTransfer { /** * 业务订单号 */ private String out_biz_no; /** * 订单总金额,单位为元,精确到小数点后两位, */ private BigDecimal trans_amount; /** * 业务产品码, * 单笔无密转账到支付宝账户固定为:TRANS_ACCOUNT_NO_PWD; * 单笔无密转账到银行卡固定为:TRANS_BANKCARD_NO_PWD; * 收发现金红包固定为:STD_RED_PACKET; */ private String product_code; /** * 描述特定的业务场景,可传的参数如下: * DIRECT_TRANSFER:单笔无密转账到支付宝/银行卡, B2C现金红包; * PERSONAL_COLLECTION:C2C现金红包-领红包 */ private String biz_scene; /** * 转账业务的标题,用于在支付宝用户的账单里显示 */ private String order_title; /** * 原支付宝业务单号。C2C现金红包-红包领取时,传红包支付时返回的支付宝单号; * B2C现金红包、单笔无密转账到支付宝/银行卡不需要该参数。 */ private String original_order_id; /** * 业务备注 */ private String remark; /** * 转账业务请求的扩展参数,支持传入的扩展参数如下: * 1、sub_biz_scene 子业务场景,红包业务必传,取值REDPACKET,C2C现金红包、B2C现金红包均需传入; * 2、withdraw_timeliness为转账到银行卡的预期到账时间,可选(不传入则默认为T1), * 取值T0表示预期T+0到账,取值T1表示预期T+1到账,因到账时效受银行机构处理影响,支付宝无法保证一定是T0或者T1到账; */ private String business_params; /** * 支付收款对象 */ private Participant payee_info; }@Data public class Participant { /** * 参与方的唯一标识 */ private String identity; /** * 参与方的标识类型,目前支持如下类型: * 1、ALIPAY_USER_ID 支付宝的会员ID * 2、ALIPAY_LOGON_ID:支付宝登录号,支持邮箱和手机号格式 */ private String identity_type; /** * 参与方真实姓名,如果非空,将校验收款支付宝账号姓名一致性。 * 当identity_type=ALIPAY_LOGON_ID时,本字段必填。 */ private String name; }
2022年07月10日
14 阅读
0 评论
0 点赞
2022-05-18
2022最新版Macbook安装配置Maven环境教程
方法一:brew 一键安装在终端输入命令 brew install maven,并自动配置好了环境变量MacBook 没安装 brew 的看下面方法二,或者先装 brew然后就可以在终端输入 mvn -v 查看版本方法二:官网下载,手动安装官网下载并解压 Maven打开 Maven 官网下载页面: https://maven.apache.org/download.cgi 比如下载最新的: apache-maven-3.8.5-bin.tar.gz解压下载的安装包到某一目录,比如:/usr/loca/注意:apache-maven-3.8.5 此名称不可以改变配置环境变量打开终端窗口,输入 open ~/.bash_profile,打开 .bash_profile 文件向文件中添加以下内容:# MAVEN PATH MAVEN_HOME=/usr/local/apache-maven-3.8.5 export MAVEN_HOME export PATH=$PATH:$MAVEN_HOME/bin添加之后退出,终端执行以下命令使配置生效source ~/.bash_profile最后终端执行 mvn -version 能看到正确版本号即配置正确
2022年05月18日
65 阅读
0 评论
1 点赞
2022-05-07
如何在MacBook上安装Java JDK并配置环境变量
搞开发的用过 MacBook 的都知道 MacBook 的好,那新入手 MacBook 后要想搞 Java 开发,肯定得重新配置一下 Java 环境呀目前来说大部分企业及开发者都会选择 JDK8,所以这里以安装 JDK8 为例子1、访问Oracle官方的Java JDK网站下载(需要登陆)点击前往官网下载,选择 macOS,M1 芯片的得选择 ARM 版或者其他 JDK 版本2、打开终端检验是否安装成功在打开的终端中输入代码 java -version 输出类似下面的即安装成功:java version "1.8.0_231" Java(TM) SE Runtime Environment (build 1.8.0_231-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)然后因为 MacBook 安装 JDK 的时候是不能配置安装目录的,所以我们需要使用命令行来查看安装目录/usr/libexec/java_home -V下面红色框框内的路径就是Java的安装目录3、配置环境变量同样是使用终端,配置一个叫 bash_profile 的文件,具体路径是 ~/.bash_profile,如果不存在这个文件就自己创建一个文件内加入以下内容(JAVA_HOME要以你自己实际的为准):JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home PATH=$JAVA_HOME/bin:$PATH:. CLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:. export JAVA_HOME export PATH export CLASSPATH保存后在终端执行 source ~/.bash_profile这时候终端输入 echo $JAVA_HOME 就会显示出 JAVA 路径
2022年05月07日
15 阅读
0 评论
0 点赞
2022-05-01
Jedis 常用API使用
pom.xml 引入 jedis 依赖<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.2</version> </dependency>1.对String操作的命令set(key, value):给数据库中名称为key的string赋予值valueget(key):返回数据库中名称为key的string的valuegetset(key, value):给名称为key的string赋予上一次的valuemget(key1, key2,…, key N):返回库中多个string(它们的名称为key1,key2…)的valuesetnx(key, value):如果不存在名称为key的string,则向库中添加string,名称为key,值为valuesetex(key, time, value):向库中添加string(名称为key,值为value)同时,设定过期时间timemset(key1, value1, key2, value2,…key N, value N):同时给多个string赋值,名称为key i的string赋值value imsetnx(key1, value1, key2, value2,…key N, value N):如果所有名称为key i的string都不存在,则向库中添加string,名称key i赋值为value iincr(key):名称为key的string增1操作incrby(key, integer):名称为key的string增加integerdecr(key):名称为key的string减1操作decrby(key, integer):名称为key的string减少integerappend(key, value):名称为key的string的值附加valuesubstr(key, start, end):返回名称为key的string的value的子串2.对List操作的命令rpush(key, value):在名称为key的list尾添加一个值为value的元素lpush(key, value):在名称为key的list头添加一个值为value的 元素llen(key):返回名称为key的list的长度lrange(key, start, end):返回名称为key的list中start至end之间的元素(下标从0开始,下同)ltrim(key, start, end):截取名称为key的list,保留start至end之间的元素lindex(key, index):返回名称为key的list中index位置的元素lset(key, index, value):给名称为key的list中index位置的元素赋值为valuelrem(key, count, value):删除count个名称为key的list中值为value的元素。count为0,删除所有值为value的元素,count>0 从头至尾删除count个值为value的元素,count<0从尾到头删除|count|个值为value的元素。lpop(key):返回并删除名称为key的list中的首元素rpop(key):返回并删除名称为key的list中的尾元素blpop(key1, key2,… key N, timeout):lpop 命令的block版本。即当timeout为0时,若遇到名称为key i的list不存在或该list为空,则命令结束。如果 timeout>0,则遇到上述情况时,等待timeout秒,如果问题没有解决,则对key i+1开始的list执行pop操作。brpop(key1, key2,… key N, timeout):rpop的block版本。参考上一命令。rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部3.对Set操作的命令sadd(key, member):向名称为key的set中添加元素membersrem(key, member) :删除名称为key的set中的元素memberspop(key) :随机返回并删除名称为key的set中一个元素smove(srckey, dstkey, member) :将member元素从名称为srckey的集合移到名称为dstkey的集合scard(key) :返回名称为key的set的基数sismember(key, member) :测试member是否是名称为key的set的元素sinter(key1, key2,…key N) :求交集sinterstore(dstkey, key1, key2,…key N) :求交集并将交集保存到dstkey的集合sunion(key1, key2,…key N) :求并集sunionstore(dstkey, key1, key2,…key N) :求并集并将并集保存到dstkey的集合sdiff(key1, key2,…key N) :求差集sdiffstore(dstkey, key1, key2,…key N) :求差集并将差集保存到dstkey的集合smembers(key) :返回名称为key的set的所有元素srandmember(key) :随机返回名称为key的set的一个元素4.对zset(sorted set)操作的命令zadd(key, score, member):向名称为key的zset中添加元素member,score用于排序。如果该元素已经存在,则根据score更新该元素的顺序。zrem(key, member) :删除名称为key的zset中的元素memberzincrby(key, increment, member) :如果在名称为key的zset中已经存在元素member,则该元素的score增加increment;否则向集合中添加该元素,其score的值为incrementzrank(key, member) :返回名称为key的zset(元素已按score从小到大排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”zrevrank(key, member) :返回名称为key的zset(元素已按score从大到小排序)中member元素的rank(即index,从0开始),若没有member元素,返回“nil”zrange(key, start, end):返回名称为key的zset(元素已按score从小到大排序)中的index从start到end的所有元素zrevrange(key, start, end):返回名称为key的zset(元素已按score从大到小排序)中的index从start到end的所有元素zrangebyscore(key, min, max):返回名称为key的zset中score >= min且score <= max的所有元素zcard(key):返回名称为key的zset的基数zscore(key, element):返回名称为key的zset中元素element的scorezremrangebyrank(key, min, max):删除名称为key的zset中rank >= min且rank <= max的所有元素zremrangebyscore(key, min, max) :删除名称为key的zset中score >= min且score <= max的所有元素zunionstore / zinterstore(dstkeyN, key1,…,keyN, WEIGHTS w1,…wN, AGGREGATE SUM|MIN|MAX):对N个zset求并集和交集,并将最后的集合保存在dstkeyN中。对于集合中每一个元素的score,在进行AGGREGATE运算前,都要乘以对于的WEIGHT参数。如果没有提供WEIGHT,默认为1。默认的AGGREGATE是SUM,即结果集合中元素的score是所有集合对应元素进行 SUM运算的值,而MIN和MAX是指,结果集合中元素的score是所有集合对应元素中最小值和最大值。5.对Hash操作的命令hset(key, field, value):向名称为key的hash中添加元素field<—>valuehget(key, field):返回名称为key的hash中field对应的valuehmget(key, field1, …,field N):返回名称为key的hash中field i对应的valuehmset(key, field1, value1,…,field N, value N):向名称为key的hash中添加元素field i<—>value ihincrby(key, field, integer):将名称为key的hash中field的value增加integerhexists(key, field):名称为key的hash中是否存在键为field的域hdel(key, field):删除名称为key的hash中键为field的域hlen(key):返回名称为key的hash中元素个数hkeys(key):返回名称为key的hash中所有键hvals(key):返回名称为key的hash中所有键对应的valuehgetall(key):返回名称为key的hash中所有的键(field)及其对应的value6.对value操作的命令exists(key):确认一个key是否存在del(key):删除一个keytype(key):返回值的类型keys(pattern):返回满足给定pattern的所有keyrandomkey:随机返回key空间的一个keyrename(oldname, newname):将key由oldname重命名为newname,若newname存在则删除newname表示的keydbsize:返回当前数据库中key的数目expire:设定一个key的活动时间(s)ttl:获得一个key的活动时间select(index):按索引查询move(key, dbindex):将当前数据库中的key转移到有dbindex索引的数据库flushdb:删除当前选择数据库中的所有keyflushall:删除所有数据库中的所有key
2022年05月01日
14 阅读
0 评论
0 点赞
2022-04-15
PHP 判断用户是否为移动端访问
直接上代码:function wp_is_mobile() { static $is_mobile = null; if (isset($is_mobile)) { return $is_mobile; } if (empty($_SERVER["HTTP_USER_AGENT"])) { $is_mobile = false; } elseif ( strpos($_SERVER["HTTP_USER_AGENT"], "Mobile") !== false || strpos($_SERVER["HTTP_USER_AGENT"], "Android") !== false || strpos($_SERVER["HTTP_USER_AGENT"], "Silk/") !== false || strpos($_SERVER["HTTP_USER_AGENT"], "Kindle") !== false || strpos($_SERVER["HTTP_USER_AGENT"], "BlackBerry") !== false || strpos($_SERVER["HTTP_USER_AGENT"], "Opera Mini") !== false || strpos($_SERVER["HTTP_USER_AGENT"], "Opera Mobi") !== false ) { $is_mobile = true; } else { $is_mobile = false; } return $is_mobile; }
2022年04月15日
13 阅读
0 评论
0 点赞
2022-04-14
给 JAVA 项目添加 AOP 日志切面
切面介绍面向切面编程是一种编程范式,它作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、权限控制、缓存控制、日志打印等等。AOP把软件的功能模块分为两个部分:核心关注点和横切关注点。业务处理的主要功能为核心关注点,而非核心、需要拓展的功能为横切关注点。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点进行分离,使用切面有以下好处:集中处理某一关注点/横切逻辑可以很方便的添加/删除关注点侵入性少,增强代码可读性及可维护性 因此当想打印请求日志时很容易想到切面,对控制层代码0侵入切面的使用【基于注解】@Aspect => 声明该类为一个注解类切点注解:@Pointcut => 定义一个切点,可以简化代码通知注解:@Before => 在切点之前执行代码@After => 在切点之后执行代码@AfterReturning => 切点返回内容后执行代码,可以对切点的返回值进行封装@AfterThrowing => 切点抛出异常后执行@Around => 环绕,在切点前后执行代码写一个请求日志切面使用 @Pointcut 定义切点@Pointcut("execution(* your_package.controller..*(..))") public void requestServer() { }@Pointcut定义了一个切点,因为是请求日志切边,因此切点定义的是Controller包下的所有类下的方法。定义切点以后在通知注解中直接使用requestServer方法名就可以了使用 @Before 再切点前执行@Before("requestServer()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); LOGGER.info("===============================Start========================"); LOGGER.info("IP : {}", request.getRemoteAddr()); LOGGER.info("URL : {}", request.getRequestURL().toString()); LOGGER.info("HTTP Method : {}", request.getMethod()); LOGGER.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); }在进入Controller方法前,打印出调用方IP、请求URL、HTTP请求类型、调用的方法名使用 @Around 打印进入控制层的入参@Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); LOGGER.info("Request Params : {}", getRequestParams(proceedingJoinPoint)); LOGGER.info("Result : {}", result); LOGGER.info("Time Cost : {} ms", System.currentTimeMillis() - start); return result; }打印了入参、结果以及耗时getRquestParams 方法:private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) { Map<String, Object> requestParams = new HashMap<>(); //参数名 String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = proceedingJoinPoint.getArgs(); for (int i = 0; i < paramNames.length; i++) { Object value = paramValues[i]; //如果是文件对象 if (value instanceof MultipartFile) { MultipartFile file = (MultipartFile) value; //获取文件名 value = file.getOriginalFilename(); } requestParams.put(paramNames[i], value); } return requestParams; }通过 @PathVariable 以及 @RequestParam 注解传递的参数无法打印出参数名,因此需要手动拼接一下参数名,同时对文件对象进行了特殊处理,只需获取文件名即可@After 方法调用后执行@After("requestServer()") public void doAfter(JoinPoint joinPoint) { LOGGER.info("=============================== End ========================"); }后置环绕这里就没有业务逻辑只是打印了End完整切面代码@Component @Aspect public class RequestLogAspect { private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class); @Pointcut("execution(* your_package.controller..*(..))") public void requestServer() { } @Before("requestServer()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); LOGGER.info("===============================Start========================"); LOGGER.info("IP : {}", request.getRemoteAddr()); LOGGER.info("URL : {}", request.getRequestURL().toString()); LOGGER.info("HTTP Method : {}", request.getMethod()); LOGGER.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); } @Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); LOGGER.info("Request Params : {}", getRequestParams(proceedingJoinPoint)); LOGGER.info("Result : {}", result); LOGGER.info("Time Cost : {} ms", System.currentTimeMillis() - start); return result; } @After("requestServer()") public void doAfter(JoinPoint joinPoint) { LOGGER.info("=============================== End ========================"); } /** * 获取入参 * @param proceedingJoinPoint * * @return * */ private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) { Map<String, Object> requestParams = new HashMap<>(); //参数名 String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = proceedingJoinPoint.getArgs(); for (int i = 0; i < paramNames.length; i++) { Object value = paramValues[i]; //如果是文件对象 if (value instanceof MultipartFile) { MultipartFile file = (MultipartFile) value; value = file.getOriginalFilename(); //获取文件名 } requestParams.put(paramNames[i], value); } return requestParams; } }但是,每个信息都打印一行,在高并发请求下确实会出现请求之间打印日志串行的问题,解决日志串行的问题只要将多行打印信息合并为一行就可以了,因此构造一个对象RequestInfo.java@Data public class RequestInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private Object result; private Long timeCost; }环绕通知方法体@Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Object result = proceedingJoinPoint.proceed(); RequestInfo requestInfo = new RequestInfo(); requestInfo.setIp(request.getRemoteAddr()); requestInfo.setUrl(request.getRequestURL().toString()); requestInfo.setHttpMethod(request.getMethod()); requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName())); requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint)); requestInfo.setResult(result); requestInfo.setTimeCost(System.currentTimeMillis() - start); LOGGER.info("Request Info : {}", JSON.toJSONString(requestInfo)); return result; }将 url、http request 这些信息组装成 RequestInfo 对象,再序列化打印对象,打印序列化对象结果而不是直接打印对象是因为序列化有更直观、更清晰,同时可以借助在线解析工具对结果进行解析.就这样在解决高并发下请求串行问题的同时添加了对异常请求信息的打印,通过使用 @AfterThrowing 注解对抛出异常的方法进行处理RequestErrorInfo.java@Data public class RequestErrorInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private RuntimeException exception; }异常通知环绕体@AfterThrowing(pointcut = "requestServer()", throwing = "e") public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); RequestErrorInfo requestErrorInfo = new RequestErrorInfo(); requestErrorInfo.setIp(request.getRemoteAddr()); requestErrorInfo.setUrl(request.getRequestURL().toString()); requestErrorInfo.setHttpMethod(request.getMethod()); requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName())); requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint)); requestErrorInfo.setException(e); LOGGER.info("Error Request Info : {}", JSON.toJSONString(requestErrorInfo)); }为什么这里要单独搞一个异常环绕通知,因为都运行出问题了,统计运行时间也没什么意义了呀,所以这里不统计耗时,而是添加了异常的打印最后放一下优化后的完整日志请求切面代码:@Component @Aspect public class RequestLogAspect { private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class); @Pointcut("execution(* your_package.controller..*(..))") public void requestServer() { } @Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long start = System.currentTimeMillis(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Object result = proceedingJoinPoint.proceed(); RequestInfo requestInfo = new RequestInfo(); requestInfo.setIp(request.getRemoteAddr()); requestInfo.setUrl(request.getRequestURL().toString()); requestInfo.setHttpMethod(request.getMethod()); requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName())); requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint)); requestInfo.setResult(result); requestInfo.setTimeCost(System.currentTimeMillis() - start); LOGGER.info("Request Info : {}", JSON.toJSONString(requestInfo)); return result; } @AfterThrowing(pointcut = "requestServer()", throwing = "e") public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); RequestErrorInfo requestErrorInfo = new RequestErrorInfo(); requestErrorInfo.setIp(request.getRemoteAddr()); requestErrorInfo.setUrl(request.getRequestURL().toString()); requestErrorInfo.setHttpMethod(request.getMethod()); requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName())); requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint)); requestErrorInfo.setException(e); LOGGER.info("Error Request Info : {}", JSON.toJSONString(requestErrorInfo)); } /** * 获取入参 * @param proceedingJoinPoint * * @return * */ private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) { //参数名 String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = proceedingJoinPoint.getArgs(); return buildRequestParam(paramNames, paramValues); } private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) { //参数名 String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = joinPoint.getArgs(); return buildRequestParam(paramNames, paramValues); } private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) { Map<String, Object> requestParams = new HashMap<>(); for (int i = 0; i < paramNames.length; i++) { Object value = paramValues[i]; //如果是文件对象 if (value instanceof MultipartFile) { MultipartFile file = (MultipartFile) value; //获取文件名 value = file.getOriginalFilename(); } requestParams.put(paramNames[i], value); } return requestParams; } @Data public class RequestInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private Object result; private Long timeCost; } @Data public class RequestErrorInfo { private String ip; private String url; private String httpMethod; private String classMethod; private Object requestParams; private RuntimeException exception; } }
2022年04月14日
19 阅读
0 评论
0 点赞
2022-04-12
Cloudflare 常用API - 自动开启5秒盾 根据平均负载自动开盾
Cloudflare API 通过编程接口公开完整的 Cloudflare 功能。您可以使用 Cloudflare API 管理帐户设置、配置产品和开发应用程序。 via 管理 API 令牌和密钥;要使用API,实现5秒盾,根据平均负载自动开盾,都需要一下几个 Cloudflare注册邮箱,Global API Key,Zone ID等;查看 API 密钥官方文档:管理 API 令牌和密钥(请自行切换语言)修改安全级别设置正常情况下,我们要设置防火墙的安全等级,我们可以在 Cloudflare 的控制面板-防火墙-设置-安全级别进行设置,简单,直观;(如上图位置);为了应对不期而至的 CC 攻击,我们可能会需要一个自动提高安全级别的策略,把安全级别从 off 提高到 under_attack;官方示例Cloudflare 常用API - 自动开启5秒盾(off, essentially_off, low, medium, high, under_attack)完整官方文档,访问 https://api.cloudflare.com/ 搜索 Security Level settingcurl -X PATCH "https://api.cloudflare.com/client/v4/zones/023e105f4ecef8ad9ca31a8372d0c353/settings/security_level" \ -H "X-Auth-Email: cloudflare 账号邮箱" \ -H "X-Auth-Key: 你的 API Key" \ -H "Content-Type: application/json" \ --data '{"value":"medium"}'现在,我们动手制作一个可执行Shell脚本,假设命名为:cf.security.sh:#!/bin/bash #cf.security.sh CFEMAIL="填写你的Cloudflare注册邮箱" CFAPIKEY="填写你的API Key" ZONEID="填写你的ZONEID" curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONEID}/settings/security_level" \ -H "X-Auth-Email: ${CFEMAIL}" \ -H "X-Auth-Key: ${CFAPIKEY}" \ -H "Content-Type: application/json" \ --data '{"value":"medium"}'其中,"value" 的值可设置为:off essentially_off low medium high under_attack执行 cf.security.sh,如果成功则返回如下提示:{"result":{"id":"security_level","value":"medium","modified_on":"2022-04-12T03:41:56.393080Z","editable":true},"success":true,"errors":[],"messages":[]}根据平均负载自动开盾以下仅做示例参考,还有很多判定方法值得考量(HTTP 502 错误数量,系统平均负载,异常的IP数量,HTTP 404 状态码数量(如果用 random 攻击的话等等))#!/bin/bash #cf.security.change.sh ##近期平均负载情况判定 usage=`echo $(cat /proc/loadavg | awk '{print $2}')` #获取当前系统平均负载值 max=2 #设置最大阈值 CFEMAIL="填写你的Cloudflare注册邮箱" CFAPIKEY="填写你的API Key" ZONEID="填写你的ZONEID" if [ `echo "$usage > $max"|bc` -eq 1 ]; then curl -X PATCH "https://api.cloudflare.com/client/v4/zones/${ZONEID}/settings/security_level" \ -H "X-Auth-Email: ${CFEMAIL}" \ -H "X-Auth-Key: ${CFAPIKEY}" \ -H "Content-Type: application/json" \ --data '{"value":"under_attack"}' fi在Linux系统中,uptime、w、top 等命令都会有系统平均负载 load average 的输出,那么什么是系统平均负载呢? 命令输出的最后内容表示在过去的 1、5、15 分钟内运行队列中的平均进程数量。上面的脚本意思是当最近5分钟系统负载大于2时,则修改 Cloudflare 防御等级为最高级(under_attack模式)最后,设置一下 Crontab 每分钟自动执行脚本即可(脚本每1分钟运行一次):脚本具体位置以你自己放置位置为准*/1 * * * * /root/cf.security.change.sh
2022年04月12日
153 阅读
0 评论
0 点赞
1
...
3
4
5
...
18