欢迎访问我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

本篇概览

  1. 准备好docker基础镜像
  2. 开发java应用
  3. 将java应用打包成package文件,集成到基础镜像中,得到最终的java应用镜像

版本信息

  • 这个java应用的涉及的版本信息如下:
  1. springboot:2.4.8
  2. javacpp:1.4.3
  3. javacv:1.4.3

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在javacv-tutorials文件夹下,如下图红框所示:

编码

  • 为了统一管理源码和jar依赖,项目采用了maven父子结构,父工程名为javacv-tutorials,其pom.xml如下,可见主要是定义了一些jar的版本:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.bolingcavalry</groupId>
  7. <artifactId>javacv-tutorials</artifactId>
  8. <packaging>pom</packaging>
  9. <version>1.0-SNAPSHOT</version>
  10. <modules>
  11. <module>face-detect-demo</module>
  12. </modules>
  13. <properties>
  14. <java.version>1.8</java.version>
  15. <maven.compiler.source>8</maven.compiler.source>
  16. <maven.compiler.target>8</maven.compiler.target>
  17. <maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
  18. <springboot.version>2.4.8</springboot.version>
  19. <!-- javacpp当前版本 -->
  20. <javacpp.version>1.4.3</javacpp.version>
  21. <!-- opencv版本 -->
  22. <opencv.version>3.4.3</opencv.version>
  23. <!-- ffmpeg版本 -->
  24. <ffmpeg.version>4.0.2</ffmpeg.version>
  25. </properties>
  26. <dependencyManagement>
  27. <dependencies>
  28. <dependency>
  29. <groupId>org.projectlombok</groupId>
  30. <artifactId>lombok</artifactId>
  31. <version>1.18.18</version>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.bytedeco</groupId>
  35. <artifactId>javacv-platform</artifactId>
  36. <version>${javacpp.version}</version>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.bytedeco</groupId>
  40. <artifactId>javacv</artifactId>
  41. <version>${javacpp.version}</version>
  42. </dependency>
  43. <!-- javacpp -->
  44. <dependency>
  45. <groupId>org.bytedeco</groupId>
  46. <artifactId>javacpp</artifactId>
  47. <version>${javacpp.version}</version>
  48. </dependency>
  49. <!-- ffmpeg -->
  50. <dependency>
  51. <groupId>org.bytedeco.javacpp-presets</groupId>
  52. <artifactId>ffmpeg-platform</artifactId>
  53. <version>${ffmpeg.version}-${javacpp.version}</version>
  54. </dependency>
  55. <dependency>
  56. <groupId>org.bytedeco.javacpp-presets</groupId>
  57. <artifactId>ffmpeg</artifactId>
  58. <version>${ffmpeg.version}-${javacpp.version}</version>
  59. </dependency>
  60. </dependencies>
  61. </dependencyManagement>
  62. </project>
  • 在javacv-tutorials下面新建名为face-detect-demo的子工程,这里面是咱们今天要开发的应用,其pom.xml如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>javacv-tutorials</artifactId>
  7. <groupId>com.bolingcavalry</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>face-detect-demo</artifactId>
  12. <packaging>jar</packaging>
  13. <dependencyManagement>
  14. <dependencies>
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-dependencies</artifactId>
  18. <version>${springboot.version}</version>
  19. <type>pom</type>
  20. <scope>import</scope>
  21. </dependency>
  22. </dependencies>
  23. </dependencyManagement>
  24. <dependencies>
  25. <!--FreeMarker模板视图依赖-->
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-freemarker</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-web</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.projectlombok</groupId>
  36. <artifactId>lombok</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-starter-test</artifactId>
  41. <scope>test</scope>
  42. </dependency>
  43. <dependency>
  44. <groupId>org.bytedeco</groupId>
  45. <artifactId>javacv-platform</artifactId>
  46. </dependency>
  47. <dependency>
  48. <groupId>org.bytedeco</groupId>
  49. <artifactId>javacv</artifactId>
  50. </dependency>
  51. <!-- javacpp -->
  52. <dependency>
  53. <groupId>org.bytedeco</groupId>
  54. <artifactId>javacpp</artifactId>
  55. </dependency>
  56. <!-- ffmpeg -->
  57. <dependency>
  58. <groupId>org.bytedeco.javacpp-presets</groupId>
  59. <artifactId>ffmpeg-platform</artifactId>
  60. </dependency>
  61. <dependency>
  62. <groupId>org.bytedeco.javacpp-presets</groupId>
  63. <artifactId>ffmpeg</artifactId>
  64. </dependency>
  65. </dependencies>
  66. <build>
  67. <plugins>
  68. <!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar -->
  69. <plugin>
  70. <groupId>org.springframework.boot</groupId>
  71. <artifactId>spring-boot-maven-plugin</artifactId>
  72. <configuration>
  73. <mainClass>com.bolingcavalry.facedetect.FaceDetectApplication</mainClass>
  74. </configuration>
  75. <executions>
  76. <execution>
  77. <goals>
  78. <goal>repackage</goal>
  79. </goals>
  80. </execution>
  81. </executions>
  82. </plugin>
  83. </plugins>
  84. </build>
  85. </project>
  • 配置文件如下,要重点关注前段模板、文件上传大小、模型文件目录等配置:
  1. ### FreeMarker 配置
  2. spring.freemarker.allow-request-override=false
  3. #Enable template caching.启用模板缓存。
  4. spring.freemarker.cache=false
  5. spring.freemarker.check-template-location=true
  6. spring.freemarker.charset=UTF-8
  7. spring.freemarker.content-type=text/html
  8. spring.freemarker.expose-request-attributes=false
  9. spring.freemarker.expose-session-attributes=false
  10. spring.freemarker.expose-spring-macro-helpers=false
  11. #设置面板后缀
  12. spring.freemarker.suffix=.ftl
  13. # 设置单个文件最大内存
  14. spring.servlet.multipart.max-file-size=100MB
  15. # 设置所有文件最大内存
  16. spring.servlet.multipart.max-request-size=1000MB
  17. # 自定义文件上传路径
  18. web.upload-path=/app/images
  19. # 模型路径
  20. opencv.model-path=/app/model/haarcascade_frontalface_default.xml
  • 前端页面文件只有一个index.ftl,请原谅欣宸不入流的前端水平,前端只有一个页面,可以提交页面,同时也是展示处理结果的页面:
  1. <!DOCTYPE html>
  2. <head>
  3. <meta charset="UTF-8" />
  4. <title>图片上传Demo</title>
  5. </head>
  6. <body>
  7. <h1 >图片上传Demo</h1>
  8. <form action="fileUpload" method="post" enctype="multipart/form-data">
  9. <p>选择检测文件: <input type="file" name="fileName"/></p>
  10. <p>周围检测数量: <input type="number" value="32" name="minneighbors"/></p>
  11. <p><input type="submit" value="提交"/></p>
  12. </form>
  13. <#--判断是否上传文件-->
  14. <#if msg??>
  15. <span>${msg}</span><br><br>
  16. <#else >
  17. <span>${msg!("文件未上传")}</span><br>
  18. </#if>
  19. <#--显示图片,一定要在img中的src发请求给controller,否则直接跳转是乱码-->
  20. <#if fileName??>
  21. <#--<img src="/show?fileName=${fileName}" style="width: 100px"/>-->
  22. <img src="/show?fileName=${fileName}"/>
  23. <#else>
  24. <#--<img src="/show" style="width: 200px"/>-->
  25. </#if>
  26. </body>
  27. </html>
  • 再来看后台代码,先是最常见的应用启动类:
  1. package com.bolingcavalry.facedetect;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class FaceDetectApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(FaceDetectApplication.class, args);
  8. }
  9. }
  • 前端上传图片后,后端要做哪些处理呢?先不贴代码,咱们把后端要做的事情捋一遍,如下图:

  • 接下来是最核心的业务类UploadController.java,web接口和业务逻辑处理都在这里面,是按照上图的流程顺序执行的,有几处要注意的地方稍后会提到:
  1. package com.bolingcavalry.facedetect.controller;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.core.io.ResourceLoader;
  6. import org.springframework.http.ResponseEntity;
  7. import org.springframework.stereotype.Controller;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RequestParam;
  10. import org.springframework.web.multipart.MultipartFile;
  11. import java.io.File;
  12. import java.io.IOException;
  13. import java.util.Map;
  14. import org.opencv.core.*;
  15. import org.opencv.imgcodecs.Imgcodecs;
  16. import org.opencv.imgproc.Imgproc;
  17. import org.opencv.objdetect.CascadeClassifier;
  18. import java.util.UUID;
  19. import static org.bytedeco.javacpp.opencv_objdetect.CV_HAAR_DO_CANNY_PRUNING;
  20. @Controller
  21. @Slf4j
  22. public class UploadController {
  23. static {
  24. // 加载 动态链接库
  25. System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  26. }
  27. private final ResourceLoader resourceLoader;
  28. @Autowired
  29. public UploadController(ResourceLoader resourceLoader) {
  30. this.resourceLoader = resourceLoader;
  31. }
  32. @Value("${web.upload-path}")
  33. private String uploadPath;
  34. @Value("${opencv.model-path}")
  35. private String modelPath;
  36. /**
  37. * 跳转到文件上传页面
  38. * @return
  39. */
  40. @RequestMapping("index")
  41. public String toUpload(){
  42. return "index";
  43. }
  44. /**
  45. * 上次文件到指定目录
  46. * @param file 文件
  47. * @param path 文件存放路径
  48. * @param fileName 源文件名
  49. * @return
  50. */
  51. private static boolean upload(MultipartFile file, String path, String fileName){
  52. //使用原文件名
  53. String realPath = path + "/" + fileName;
  54. File dest = new File(realPath);
  55. //判断文件父目录是否存在
  56. if(!dest.getParentFile().exists()){
  57. dest.getParentFile().mkdir();
  58. }
  59. try {
  60. //保存文件
  61. file.transferTo(dest);
  62. return true;
  63. } catch (IllegalStateException e) {
  64. // TODO Auto-generated catch block
  65. e.printStackTrace();
  66. return false;
  67. } catch (IOException e) {
  68. // TODO Auto-generated catch block
  69. e.printStackTrace();
  70. return false;
  71. }
  72. }
  73. /**
  74. *
  75. * @param file 要上传的文件
  76. * @return
  77. */
  78. @RequestMapping("fileUpload")
  79. public String upload(@RequestParam("fileName") MultipartFile file, @RequestParam("minneighbors") int minneighbors, Map<String, Object> map){
  80. log.info("file [{}], size [{}], minneighbors [{}]", file.getOriginalFilename(), file.getSize(), minneighbors);
  81. String originalFileName = file.getOriginalFilename();
  82. if (!upload(file, uploadPath, originalFileName)){
  83. map.put("msg", "上传失败!");
  84. return "forward:/index";
  85. }
  86. String realPath = uploadPath + "/" + originalFileName;
  87. Mat srcImg = Imgcodecs.imread(realPath);
  88. // 目标灰色图像
  89. Mat dstGrayImg = new Mat();
  90. // 转换灰色
  91. Imgproc.cvtColor(srcImg, dstGrayImg, Imgproc.COLOR_BGR2GRAY);
  92. // OpenCv人脸识别分类器
  93. CascadeClassifier classifier = new CascadeClassifier(modelPath);
  94. // 用来存放人脸矩形
  95. MatOfRect faceRect = new MatOfRect();
  96. // 特征检测点的最小尺寸
  97. Size minSize = new Size(32, 32);
  98. // 图像缩放比例,可以理解为相机的X倍镜
  99. double scaleFactor = 1.2;
  100. // 执行人脸检测
  101. classifier.detectMultiScale(dstGrayImg, faceRect, scaleFactor, minneighbors, CV_HAAR_DO_CANNY_PRUNING, minSize);
  102. //遍历矩形,画到原图上面
  103. // 定义绘制颜色
  104. Scalar color = new Scalar(0, 0, 255);
  105. Rect[] rects = faceRect.toArray();
  106. // 没检测到
  107. if (null==rects || rects.length<1) {
  108. // 显示图片
  109. map.put("msg", "未检测到人脸");
  110. // 文件名
  111. map.put("fileName", originalFileName);
  112. return "forward:/index";
  113. }
  114. // 逐个处理
  115. for(Rect rect: rects) {
  116. int x = rect.x;
  117. int y = rect.y;
  118. int w = rect.width;
  119. int h = rect.height;
  120. // 单独框出每一张人脸
  121. Imgproc.rectangle(srcImg, new Point(x, y), new Point(x + w, y + w), color, 2);
  122. }
  123. // 添加人脸框之后的图片的名字
  124. String newFileName = UUID.randomUUID().toString() + ".png";
  125. // 保存
  126. Imgcodecs.imwrite(uploadPath + "/" + newFileName, srcImg);
  127. // 显示图片
  128. map.put("msg", "一共检测到" + rects.length + "个人脸");
  129. // 文件名
  130. map.put("fileName", newFileName);
  131. return "forward:/index";
  132. }
  133. /**
  134. * 显示单张图片
  135. * @return
  136. */
  137. @RequestMapping("show")
  138. public ResponseEntity showPhotos(String fileName){
  139. if (null==fileName) {
  140. return ResponseEntity.notFound().build();
  141. }
  142. try {
  143. // 由于是读取本机的文件,file是一定要加上的, path是在application配置文件中的路径
  144. return ResponseEntity.ok(resourceLoader.getResource("file:" + uploadPath + "/" + fileName));
  145. } catch (Exception e) {
  146. return ResponseEntity.notFound().build();
  147. }
  148. }
  149. }
  • UploadController.java的代码,有以下几处要关注:
  1. 在静态方法中通过System.loadLibrary加载本地库函,实际开发过程中,这里是最容易报错的地方,一定要确保-Djava.library.path参数配置的路径中的本地库是正常可用的,前文制作的基础镜像中已经准比好了这些本地库,因此只要确保-Djava.library.path参数配置正确即可,这个配置在稍后的Dockerfile中会提到
  2. public String upload方法是处理人脸检测的代码入口,内部按照前面分析的流程顺序执行
  3. new CascadeClassifier(modelPath)是根据指定的模型来实例化分类器,模型文件是从GitHub下载的,opencv官方提前训练好的模型,地址是:https://github.com/opencv/opencv/tree/master/data/haarcascades
  4. 看似神奇的人脸检测功能,实际上只需一行代码classifier.detectMultiScale,就能得到每个人脸在原图中的矩形位置,接下来,咱们只要按照位置在原图上添加矩形框即可
  • 现在代码已经写完了,接下来将其做成docker镜像

docker镜像制作

  • 首先是编写Dockerfile:
  1. # 基础镜像集成了openjdk8和opencv3.4.3
  2. FROM bolingcavalry/opencv3.4.3:0.0.3
  3. # 创建目录
  4. RUN mkdir -p /app/images && mkdir -p /app/model
  5. # 指定镜像的内容的来源位置
  6. ARG DEPENDENCY=target/dependency
  7. # 复制内容到镜像
  8. COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
  9. COPY ${DEPENDENCY}/META-INF /app/META-INF
  10. COPY ${DEPENDENCY}/BOOT-INF/classes /app
  11. # 指定启动命令
  12. ENTRYPOINT ["java","-Djava.library.path=/opencv-3.4.3/build/lib","-cp","app:app/lib/*","com.bolingcavalry.facedetect.FaceDetectApplication"]
  • 上述Dockerfile内容很简单,就是一些复制文件的处理,只有一处要格外注意:启动命令中有个参数-Djava.library.path=/opencv-3.4.3/build/lib,指定了本地so库的位置,前面的java代码中,System.loadLibrary加载的本地库就是从这个位置加载的,咱们用的基础镜像是bolingcavalry/opencv3.4.3:0.0.3,已经在该位置准备好了opencv的所有本地库

  • 在父工程目录下执行mvn clean package -U,这是个纯粹的maven操作,和docker没有任何关系

  • 进入face-detect-demo目录,执行以下命令,作用是从jar文件中提取class、配置文件、依赖库等内容到target/dependency目录:

  1. mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
  • 最后,在Dockerfile文件所在目录执行命令docker build -t bolingcavalry/facedetect:0.0.1 .(命令的最后有个点,不要漏了),即可完成镜像制作

  • 如果您有hub.docker.com的账号,还可以通过docker push命令把镜像推送到中央仓库,让更多的人用到:

  • 最后,再来回顾一下《三分钟极速体验:Java版人脸检测》一文中启动docker容器的命令,如下可见,通过两个-v参数,将宿主机的目录映射到容器中,因此,容器中的/app/images和/app/model可以保持不变,只要能保证宿主机的目录映射正确即可:

  1. docker run \
  2. --rm \
  3. -p 18080:8080 \
  4. -v /root/temp/202107/17/images:/app/images \
  5. -v /root/temp/202107/17/model:/app/model \
  6. bolingcavalry/facedetect:0.0.1

需要重点注意的地方

  • 请大家关注pom.xml中和javacv相关的几个库的版本,这些版本是不能随便搭配的,建议按照文中的来,就算要改,也请在maven中央仓库检查您所需的版本是否存在;

  • 至此,《Java版人脸检测》从体验到开发详解都完成了,小小的功能涉及到不少知识点,也让我们体验到了javacv的便捷和强大,借助docker将环境配置和应用开发分离开来,降低了应用开发和部署的难度(不再花时间到jdk和opencv的部署上),如果您正在寻找简单易用的javacv开发和部署方案,希望本文能给您提供参考;

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界...

https://github.com/zq2599/blog_demos

Java版人脸检测详解下篇:编码的更多相关文章

  1. Java版人脸检测详解上篇:运行环境的Docker镜像(CentOS+JDK+OpenCV)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. 三分钟极速体验:Java版人脸检测

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. java ssl https 连接详解 生成证书 tomcat keystone

    java ssl https 连接详解 生成证书 我们先来了解一下什么理HTTPS 1. HTTPS概念 1)简介 HTTPS(全称:Hypertext Transfer Protocol over ...

  4. Java并发关键字Volatile 详解

    Java并发关键字Volatile 详解 问题引出: 1.Volatile是什么? 2.Volatile有哪些特性? 3.Volatile每个特性的底层实现原理是什么? 相关内容补充: 缓存一致性协议 ...

  5. 国际化,java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

  6. java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

  7. java.util.ResourceBundle使用详解(转)

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

  8. java web.xml配置详解(转)

    源出处:java web.xml配置详解 1.常规配置:每一个站的WEB-INF下都有一个web.xml的设定文件,它提供了我们站台的配置设定. web.xml定义: .站台的名称和说明 .针对环境参 ...

  9. 【转】Eclipse Java注释模板设置详解

    Eclipse Java注释模板设置详解   设置注释模板的入口: Window->Preference->Java->Code Style->Code Template 然后 ...

随机推荐

  1. Mybatis(一)——HelloWorld

    本人的博客一向保持"傻瓜式"的风格. 循序渐进学Mybatis,先konw how,再konw why.先整体,再细节! 本文不讲难懂的概念,先通过一个案例,希望读者跟着本文一步一 ...

  2. salesforce零基础学习(一百零六)Dynamic Form

    本篇参考:https://trailblazer.salesforce.com/ideaview?id=08730000000BroxAAC https://help.salesforce.com/s ...

  3. 使用dubbo-go搭建dubbo接口测试平台

    背景 http接口测试只需要一个curl命令,但dubbo协议没有这样的现成接口测试工具.通常公司内的dubbo控制台或其他平台会集成一个dubbo接口测试工具. 调用一个dubbo接口,需要知道服务 ...

  4. 定时器及PWM

    1 定时器 1.1 定时器分类 对于STM32来说,定时器可分为基本定时器.通用定时器.高级定时器三类,后者包括前者的全部功能.以stm32f1系列为例,TIM6和TIM7为基本定时器,TIM2~TI ...

  5. Oracle列值拼接

    最近在学习的过程中,发现一个挺有意思的函数,它可实现对列值的拼接.下面我们来看看其具体用法. 用法: 对其作用,官方文档的解释如下: For a specified measure, LISTAGG  ...

  6. 洛谷P1056——排座椅(模拟,贪心,排序)

    https://www.luogu.org/problem/show?pid=1056 题目描述 上课的时候总会有一些同学和前后左右的人交头接耳,这是令小学班主任十分头疼的一件事情.不过,班主任小雪发 ...

  7. 在PHP中灵活使用foreach+list处理多维数组

    先抛出问题,有时候我们接收到的参数是多维数组,我们需要将他们转成普通的数组,比如: $arr = [ [1, 2, [3, 4]], [5, 6, [7, 8]], ]; 我们需要的结果是元素1变成1 ...

  8. js不记录某个url链接历史访问,返回时不返回该链接

    (function(){ var fnUrlReplace = function (eleLink) { if (!eleLink) { return; } var href = eleLink.hr ...

  9. Hystrix配置实战及feign超时配置失效

    一.feign超时配置失效 最近项目上遇见feign超时配置总是失效.导致feign调用超过2s之后就会超时,会进行自动重试,重复调用两次服务,并且还是指定接口.这就更加奇怪.最后通过观察以及源码调试 ...

  10. 『GoLang』错误处理

    Go 没有像 Java 和 .NET 那样的 try/catch 异常机制:不能执行抛异常操作.但是有一套 defer-panic-and-recover 机制. Go 的设计者觉得 try/catc ...