首页
关于
留言
Search
1
网盘挂载程序sharelist美化教程
3,763 阅读
2
红米 AX3000 (AX6) 路由器解锁 SSH 教程
3,092 阅读
3
小米路由器 AX3600 开启SSH教程,官方固件即可安装 ShellClash开启科学上网
848 阅读
4
小米路由器Openwrt固件修改代码超频至1100MHZ
520 阅读
5
编译带PassWall和SSR-plus插件的Openwrt系统
323 阅读
前端
Vue
React
后端
Java
Python
PHP
数据库
运维
杂谈
小程序
影视资源
登录
Search
标签搜索
Java
Linux
Mysql
IDEA
Docker
Debian
Springboot
JavaScript
Cloudflare
Maven
Windows
MacBook
JS
SQL
CSS
Map
List
Debian10
容器
小米路由器
William
累计撰写
118
篇文章
累计收到
464
条评论
首页
栏目
前端
Vue
React
后端
Java
Python
PHP
数据库
运维
杂谈
小程序
影视资源
页面
关于
留言
搜索到
33
篇与
的结果
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日
7 阅读
0 评论
0 点赞
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日
4 阅读
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日
6 阅读
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日
6 阅读
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日
8 阅读
0 评论
0 点赞
2022-03-24
PHP json_encode函数的参数说明与用法
php使用json_encode()函数可以把数组,对象转化成JSON格式的字符串,用于和其它页面的数据交互。今天就说一说PHP中json_encode()函数的定义与使用方法。函数定义json_encode():可以把数组,对像转化成json格式的字符串json_encode ( value,options,depth)参数:value:要被转换的数据,可以是对象,数组或字符串options:二进制常量,规定一些要转换的字符串的形式(具体,看文未说明)depth:设置最大深度。 必须大于0。注意:depth 参数在一些文档或教材中是不存在,但在PHP官方文档中是有介绍的,在平时,不建议使用,略过即可。数组转json利用 json_encode(函数,可以很轻松的将一维数组,以及二维数组转换成 JSON 字符串,如以下的示例代码。转化一维数组示例代码:<?php // William's Blog // 一维数组转成JSON $arr = array( 'Name' => 'iyume', 'PageHome' => 'http://blog.iyume.top', ); var_dump(json_encode($arr)); ?>由于json_encode()函数返回的结果是字符串形式的,可以利用var_dump()函数来打印转化二维数组示例代码:<?php // William's Blog // 二维数组转成JSON $arr = array( 'Name' => 'iyume', 'PageHome' => 'http://blog.iyume.top', 'About' => array( 'Major' => 'PHP', 'Age' => '18', 'Address' => 'Shang Hai', ), ); var_dump(json_encode($arr)); ?>打印结果:string(113) "{"Name":"iyume","PageHome":"http:\/\/blog.iyume.top","About":{"Major":"PHP","Age":"18","Address":"Shang Hai"}}"中文不转码<?php // William's Blog $arr = array( 'Name' => '云梦', 'PageHome' => '云梦博客', ); var_dump(json_encode($arr, JSON_UNESCAPED_UNICODE)); ?>options 参数列表JSON_HEX_TAG所有的 < 和 > 转换成 \u003C 和 \u003E。 自 PHP 5.3.0 起生效。JSON_HEX_AMP所有的 & 转换成 \u0026。 自 PHP 5.3.0 起生效。JSON_HEX_APOS所有的 ' 转换成 \u0027。 自 PHP 5.3.0 起生效。JSON_HEX_QUOT所有的 " 转换成 \u0022。 自 PHP 5.3.0 起生效。JSON_FORCE_OBJECT使一个非关联数组输出一个类(Object)而非数组。 在数组为空而接受者需要一个类(Object)的时候尤其有用。 自 PHP 5.3.0 起生效。JSON_NUMERIC_CHECK将所有数字字符串编码成数字(numbers)。 自 PHP 5.3.3 起生效。JSON_PRETTY_PRINT用空白字符格式化返回的数据。 自 PHP 5.4.0 起生效。JSON_UNESCAPED_SLASHES不要编码 /。 自 PHP 5.4.0 起生效。JSON_UNESCAPED_UNICODE以字面编码多字节 Unicode 字符(默认是编码成 \uXXXX)。 自 PHP 5.4.0 起生效。连在一起可以是:echo json_encode($array, JSON_NUMERIC_CHECK | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
2022年03月24日
7 阅读
0 评论
0 点赞
2022-03-05
MQ部分知识点 - 消息丢失、重复消费、消费顺序、堆积、事务、高可用
消息队列的应用场景?答案:1、异步处理 2、流量削峰填谷 3、应用解耦 4、消息通讯异步处理。将一个请求链路中的非核心流程,拆分出来,异步处理,减少主流程链路的处理逻辑,缩短RT,提升吞吐量。如:注册新用户发短信通知。削峰填谷。避免流量暴涨,打垮下游系统,前面会加个消息队列,平滑流量冲击。比如:秒杀活动。生活中像电源适配器也是这个原理。应用解耦。两个应用,通过消息系统间接建立关系,避免一个系统宕机后对另一个系统的影响,提升系统的可用性。如:下单异步扣减库存消息通讯。内置了高效的通信机制,可用于消息通讯。如:点对点消息队列、聊天室。常用的消息框架有哪些?答案:ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaQ,RocketMQ、Pulsar 等MQ技术选型?答案:对比了 Kafka、RocketMQ 、Pulsar 三个框架,时耗、吞吐量、可靠性、事务、副本同步策略、多租户、动态扩容、故障恢复等评估指标。如果对于消息队列的功能和性能要求不是很高,那么RabbitMQ就够了,开箱即用。如果系统使用消息队列主要场景是处理在线业务,比如在交易系统中用消息队列传递订单,RocketMQ 的低延迟和金融级的稳定性就可以满足。要处理海量的消息,像收集日志、监控信息或是前端的埋点这类数据,或是你的应用场景大量使用 了大数据、流计算相关的开源产品,那 Kafka 就是最合适的了。如果数据量很大,同时不希望有 Kafka 的高延迟,刚好业务场景是金融场景。RocketMQ 对 Topic 运营不太友好,特别是不支持按 Topic 删除失效消息,以及不具备宕机 Failover 能力。那么 Pulsar 可能就是你的一个选择了。消息模型有哪些?答案:1、点对点模式 2、发布/订阅模式如何保证 MQ 消息不丢失?答案:在了解消息中间件的运作模式后,主要从三个方面来考虑这个问题:1、生产端,不丢失消息2、MQ服务端,存储本身不丢失消息3、消费端,不丢失消息如何解决消息的重复消费?答案:生产端为了保证消息发送成功,可能会重复推送(直到收到成功ACK),会产生重复消息。但是一个成熟的MQ Server框架一般会想办法解决,避免存储重复消息(比如:空间换时间,存储已处理过的message_id),给生产端提供一个幂等性的发送消息接口。但是消费端却无法根本解决这个问题,在高并发标准要求下,拉取消息+业务处理+提交消费位移需要做事务处理,另外消费端服务可能宕机,很可能会拉取到重复消息。所以,只能业务端自己做控制,对于已经消费成功的消息,本地数据库表或Redis缓存业务标识,每次处理前先进行校验,保证幂等。如何保证 MQ消息是有序的?答案:有些业务有上下文要求,比如:电商行业的下单、付款、发货、确认收货,每个环节都会发送消息。而消费端拉取并消费消息时,也是希望按正常的状态机流程进行。所以对消息就有了顺序要求。解决思路:1、该 topic 强制采用一个分区,所有消息放到一个队列里,这样能达到全局顺序性。但是会损失高并发特性。2、局部有序,采用路由机制,将同一个订单的不同状态消息存储在一个分区 partition,单线程消费。比如Kafka就提供了一个接口扩展 org.apache.kafka.clients.Partitioner,方便开发人员按照自己的业务场景来定制路由规则。消息堆积如何处理?答案:主要是消息的消费速度跟不上生产速度,从而导致消息堆积。解决思路:1、可能是刚上线的业务,或者大促活动,流量评估不到位,这时需要增加消费组的机器数量,提升整体消费能力2、也可能是消费端的问题,正常情况,一条消息处理需要10ms,但是优化不到位或者线上bug,现在要500ms,那么消费端的整体处理速度会下降50倍。这时,我们就要针对性的排查业务代码。Tom哥之前带的团队就有小伙伴出现这个问题,当时是数据库的一条sql没有命中索引,导致单条消息处理耗时拉长,进而导致消息堆积,线上报警,不过凭我们丰富的经验,很快就定位解决了。如何保证数据一致性问题?答案:为了解耦,引入异步消息机制。先进行本地数据库操作,处理成功后,再发送MQ消息,由消费端进行后续操作。比如:电商订单下单成功后,要通知扣减库存。这两者一定要保证事务操作,否则就会出现数据不一致问题。这时候,我们就需要引入事务消息来解决这个问题。另外,在消费环节,也可能出现数据不一致情况。我们可以采用最终一致性原则,增加重试机制。事务消息是如何实现?答案:1、生产者先发送一条半事务消息到MQ2、MQ收到消息后返回ack确认3、生产者开始执行本地事务4、if 本地事务执行成功,发送commit到MQ;失败,发送rollback5、如果MQ⻓时间未收到生产者的二次确认commit或rollback,MQ对生产者发起反向回查6、生产者查询事务执行最终状态7、根据查询事务状态,再次提交二次确认MQ框架 如何实现高吞吐量?答案:1、消息的批量处理2、消息压缩,节省传输带宽和存储空间3、零拷贝4、磁盘的顺序写入5、page cache 页缓存,由操作系统异步将缓存中的数据刷到磁盘,以及高效的内存读取6、分区设计,一个逻辑topic下面挂载N个分区,每个分区可以对应不同的机器消费消息,并发设计。Kafka 为什么不支持读写分离?答案:我们知道,生产端写入消息、消费端拉取消息都是与leader 副本交互的,并没有像mysql数据库那样,master负责写,slave负责读。这种设计主要是从两个方面考虑:1、数据一致性。一主多从,leader副本的数据同步到follower副本有一定的延时,因此每个follower副本的消息位移也不一样,而消费端是通过消费位移来控制消息拉取进度,多个副本间要维护同一个消费位移的一致性。如果引入分布式锁,保证并发安全,非常耗费性能。2、实时性。leader副本的数据同步到follower副本有一定的延时,如果网络较差,延迟会很严重,无法满足实时性业务需求。综上考虑,读写操作都是针对 leader 副本进行的,而 follower 副本主要是用于数据的备份。MQ框架如何做到高可用性?答案:以Kafka框架为例,其他的MQ框架原理类似。Kafka 由多个 broker 组成,每个 broker 是一个节点。你创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 存放在不同的 broker 上,每个 partition 存放一部分数据,每个 partition 有多个 replica 副本。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。如果某个 broker 宕机了,没事儿,那个 broker 上面的 partition 在其他机器上都有副本,此时会从 follower 中重新选举一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就是所谓的高可用性。
2022年03月05日
6 阅读
0 评论
0 点赞
2022-02-22
Springboot使用Thymeleaf时静态资源不起作用解决方法
Springboot项目将HTML与CSS,JS都分别放入了resources文件夹下的templates与static文件夹下了。 HTML中CSS与JS的引用部分需要做修改JS部分,增加了 th:src="@{路径}",另外,图片的引用也需要添加<script th:src="@{/js/jquery.min.js}" src="../static/js/jquery.min.js" th:></script> <script th:src="@{/js/semantic.min.js}" src="../static/js/semantic.min.js"></script>CSS部分,增加了 th:href="@{路径}" <link rel="stylesheet" href="../static/css/semantic.min.css" th:href="@{/css/semantic.min.css}"> <link rel="stylesheet" href="../static/css/me.css" th:href="@{/css/me.css}">然后在 application.yml 中增加配置: web: resources: static-locations: classpath:/static/,classpath:/templates/另外,最好也修改 thymeleaf 的缓存为 false thymeleaf: mode: HTML cache: false最后rebuild一下项目,通常问题就解决了。
2022年02月22日
1 阅读
0 评论
0 点赞
1
2
...
5