Fork me on GitHub

JackLin的博客

当前位置:首页 > 分类

SpringBoot 10 JavaWeb 2 Android 0 源码分析 1 Thymeleaf 1 MyBatis 0 SSM 1 翻译 0 数据库设计 9 Gradle 1 Test 1 Tomcat 1 前端设计 2 ElasticSearch 2 阿里巴巴 9 Redis 3 Docker 1 实验报告 1 SpringCloud相关 1 生活 1

阿里云AI训练营_Day05_创意日_图片识别文字

  • 2020-06-08
  • 176
  • SpringBoot
## 项目介绍 参加阿里云AI训练营的第5天,也是最后一天了。今天是创意日,想做什么就做什么,没有题目约束。 那么我打算做一个简单的识别图片中的文字的应用,并部署上线,以后也可能经常会用到。 ## 项目用到的文档地址 阿里云达摩院视觉开放平台:https://vision.aliyun.com/ 阿里云视觉开放平台 “通用识别” 地址:https://help.aliyun.com/document_detail/151896.html?spm=a2c4g.11186623.6.620.44da1ded5yuZbF ## 项目开始 ### (1)说明 经过前面几天的训练,自我感觉良好。已经对阿里云的视觉开放平台比较熟悉了,也熟悉使用提供的API的一些基础的步骤,感觉就是这些套路。 那么就不啰嗦了,直接上代码。由于只用到了一个场景,所以代码也比较简单,记得测试前要到阿里云开启 “文字识别服务” 哦! ![](http://image.linkaiblog.top/image-20200608223154211.png) ### (2)导入Maven依赖 ```xml <dependency> <groupId>com.aliyun</groupId> <artifactId>ocr</artifactId> <version>1.0.3</version> </dependency> <!-- 阿里巴巴的 fstjson ,和 jackson 的功能类似,用来处理 json字符串--> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> ``` ### (3)创建Client,Config类 > AIService.java ```java private Client ocrClient; private RuntimeOptions runtimeOptions; @Value("${accessKeyId}") private String accessKeyId; @Value("${accessKeySecret}") private String accessKeySecret; @PostConstruct private void init() throws Exception { Config config = new Config(); config.type = "access_key"; config.regionId = "cn-shanghai"; config.accessKeyId = accessKeyId; config.accessKeySecret = accessKeySecret; config.endpoint = "ocr.cn-shanghai.aliyuncs.com"; ocrClient = new Client(config); runtimeOptions = new RuntimeOptions(); } ``` ### (4)关键代码,调用API函数 这里由于我们需要上传图片,所以参数选择了一个 InputStream,直接从文件中获取输入流。 看文档我们知道,从图片中识别的文字会被封装在一个 data 里面,我们从这里面获取数据即可。 ![](http://image.linkaiblog.top/image-20200608223740407.png) 同时,经过测试,文字识别的过程是一行一行识别的,每行识别出来的文字都成为封装在数组的一个元素里面,所以我们定义一个 StringBuffer 对象,用来拼接这些生成的文字。 > AiService.java ```java public String myRecognizeCharacter(InputStream is) throws Exception { RecognizeCharacterAdvanceRequest request = new RecognizeCharacterAdvanceRequest(); request.imageURLObject = is; request.minHeight = 10; request.outputProbability = true; RecognizeCharacterResponse response = ocrClient.recognizeCharacterAdvance(request, runtimeOptions); StringBuffer result = new StringBuffer(); for (RecognizeCharacterResponse.RecognizeCharacterResponseDataResults item:response.data.results ) { result.append(item.text + "\n"); } return result.toString(); } ``` ### (5)文件上传逻辑 我们在 Service 层实现文件上传的逻辑,然后在 Controller 层调用,并返回上传之后的文件名。 > AiService.java ```java public String uploadImage(MultipartFile file, HttpServletRequest request) { //获取文件名 : file.getOriginalFilename(); String uploadFileName = file.getOriginalFilename(); System.out.println("上传文件名 : "+uploadFileName); String uuid = UUID.randomUUID().toString().replaceAll("-", ""); System.out.println(uuid); String newFileName = "AI-Word-" + uuid + "-" + uploadFileName; //上传路径保存设置 UUID String path = request.getServletContext().getRealPath("/upload"); // String path = "src/main/resources/static/upload"; //如果路径不存在,创建一个 File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } System.out.println("上传文件保存地址:"+realPath); InputStream is = null; //文件输入流 OutputStream os = null; try { is = file.getInputStream(); os = new FileOutputStream(new File(realPath, newFileName)); //文件输出流 //读取写出 int len=0; byte[] buffer = new byte[1024]; while ((len=is.read(buffer))!=-1){ os.write(buffer,0,len); os.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { try { os.close(); } catch (IOException e) { e.printStackTrace(); } try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return newFileName; } ``` ### (6)Controller 层的逻辑 下面我们来看一下 Controller 层的代码,主要就是实现视图的跳转。然后调用 Service 层,获得数据并放在 Model 中并返回给前端 > AiController.jva ```java @Controller public class AIController { @Autowired private AIService aiService; @RequestMapping("/") public String toMain() { return "main"; } @RequestMapping("/fileupload") public String fileUpload(@RequestParam("file") MultipartFile file , HttpServletRequest request, Model model) throws IOException { // 1. 文件上传,返回上传之后新的文件名称 String newFileName = aiService.uploadImage(file, request); // 2. 图片中文字内容识别 InputStream is = file.getInputStream(); String resultWord = null; try { resultWord = aiService.myRecognizeCharacter(is); } catch (Exception e) { e.printStackTrace(); } model.addAttribute("result", "/upload/" + newFileName); model.addAttribute("word", resultWord); return "result"; } } ``` ### (7) 前端部分 最后再来看一下前端部分代码: > Result.html ```html <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <style> div { width: 400px; border: 1px solid #ff5037; margin-top: 70px; } img { height: 400px; } </style> </head> <body> <div th:text="${word}"></div> <img th:src="@{${result}}" alt=""> </body> </html> ``` ## 测试 先进入主页面,选择图片并点击上传按钮: ![](http://image.linkaiblog.top/image-20200608224523080.png) 识别出来的结果: ![](http://image.linkaiblog.top/_2020060822581958SS.png) ## 阿里云高校计划 最后在贴一张阿里云的广告:”阿里云高校计划“,快来加入我们吧! ![](http://image.linkaiblog.top/image-20200606222043666.png)

(转载)接近8000字的Spring/SpringBoot常用注解总结!安排!

  • 2020-05-18
  • 143
  • SpringBoot
非原创 > 原文作者:SnailClimb 本文转载自:https://www.imooc.com/article/304149 ## 前言 大家好,我是 Guide 哥!这是我的 221 篇优质原创文章。如需转载,请在文首注明地址,蟹蟹! 本文已经收录进我的 75K Star 的 Java 开源项目 JavaGuide:https://github.com/Snailclimb/JavaGuide 可以毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解我都说了具体用法,掌握搞懂,使用 SpringBoot 来开发项目基本没啥大问题了! 整个目录如下,内容有点多: ![](http://img3.sycdn.imooc.com/5eaa987f0001279810802201.jpg) 为什么要写这篇文章? 最近看到网上有一篇关于 SpringBoot 常用注解的文章被转载的比较多,我看了文章内容之后属实觉得质量有点低,并且有点会误导没有太多实际使用经验的人(这些人又占据了大多数)。所以,自己索性花了大概 两天时间简单总结一下了。 因为我个人的能力和精力有限,如果有任何不对或者需要完善的地方,请帮忙指出!Guide 哥感激不尽! ## 1.`@ SpringBootApplication` 这里先单独拎出 `@ SpringBootApplication` 注解说一下,虽然我们一般不会主动去使用它。 Guide 哥:这个注解是 Spring Boot 项目的基石,创建 SpringBoot 项目之后会默认在主类加上。 ``` @SpringBootApplication public class SpringSecurityJwtGuideApplication { public static void main(java.lang.String[] args) { SpringApplication.run(SpringSecurityJwtGuideApplication.class, args); } } ``` 我们可以把 `@ SpringBootApplication`看作是 `@ Configuration`、`@ EnableAutoConfiguration`、`@ ComponentScan` 注解的集合。 ``` package org.springframework.boot.autoconfigure; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { ...... } package org.springframework.boot; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { } ``` 根据 SpringBoot 官网,这三个注解的作用分别是: - `@ EnableAutoConfiguration`:启用 SpringBoot 的自动配置机制 - `@ ComponentScan`: 扫描被`@ Component` (`@ Service`,`@ Controller`)注解的 bean,注解默认会扫描该类所在的包下所有的类。 - `@ Configuration`:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类 ## 2. Spring Bean 相关 ### 2.1. `@ Autowired` 自动导入对象到类中,被注入进的类同样要被 Spring 容器管理比如:Service 类注入到 Controller 类中。 ``` @Service public class UserService { ...... } @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; ...... } ``` ### 2.2. `Component`,`@ Repository`,`@ Service`, `@ Controller` 我们一般使用 `@ Autowired` 注解让 Spring 容器帮我们自动装配 bean。要想把类标识成可用于 `@ Autowired` 注解自动装配的 bean 的类,可以采用以下注解实现: - `@ Component` :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component 注解标注。 - `@ Repository` : 对应持久层即 Dao 层,主要用于数据库相关操作。 - `@ Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。 - `@ Controller` : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。 ### 2.3. `@ RestController` `@ RestController`注解是`@ Controller`和`@ ResponseBody`的合集,表示这是个控制器 bean,并且是将函数的返回值直 接填入 HTTP 响应体中,是 REST 风格的控制器。 Guide 哥:现在都是前后端分离,说实话我已经很久没有用过`@ Controller`。如果你的项目太老了的话,就当我没说。 单独使用 `@ Controller` 不加 `@ ResponseBody`的话一般使用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。`@ Controller` +`@ ResponseBody` 返回 JSON 或 XML 形式数据 关于`@ RestController` 和 `@ Controller`的对比,请看这篇文章: [@ RestController vs @ Controller](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485544&idx=1&sn=3cc95b88979e28fe3bfe539eb421c6d8&chksm=cea247a3f9d5ceb5e324ff4b8697adc3e828ecf71a3468445e70221cce768d1e722085359907&token=1725092312&lang=zh_CN#rd "66") ### 2.4. `@ Scope` 声明 Spring Bean 的作用域,使用方法: ``` @Bean @Scope("singleton") public Person personSingleton() { return new Person(); } ``` 四种常见的 Spring Bean 的作用域: - singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。 prototype : 每次请求都会创建一个新的 bean 实例。 - request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。 - session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。 ### 2.5. `Configuration` 一般用来声明配置类,可以使用 @Component注解替代,不过使用Configuration注解声明配置类更加语义化。 ``` @Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } } ``` ## 3. 处理常见的 HTTP 请求类型 **5 种常见的请求类型: ** - GET :请求从服务器获取特定资源。举个例子:GET /users(获取所有学生) - POST :在服务器上创建一个新的资源。举个例子:POST /users(创建学生) - PUT :更新服务器上的资源(客户端提供更新后的整个资源)。举个例子:PUT /users/12(更新编号为 12 的学生) - DELETE :从服务器删除特定的资源。举个例子:DELETE /users/12(删除编号为 12 的学生) PATCH :更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少,这里就不举例子了。 ### 3.1. GET 请求 @ GetMapping("users") 等价于@ RequestMapping(value="/users",method=RequestMethod.GET) ``` @GetMapping("/users") public ResponseEntity<List<User>> getAllUsers() { return userRepository.findAll(); } ``` ### 3.2. POST 请求 @ PostMapping("users") 等价于@ RequestMapping(value="/users",method=RequestMethod.POST) 关于`@ RequestBody`注解的使用,在下面的“前后端传值”这块会讲到。 ``` @PostMapping("/users") public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) { return userRespository.save(user); } ``` ### 3.3. PUT 请求 @ PutMapping("/users/{userId}") 等价于@ RequestMapping(value="/users/{userId}",method=RequestMethod.PUT) ``` @PutMapping("/users/{userId}") public ResponseEntity<User> updateUser(@PathVariable(value = "userId") Long userId, @Valid @RequestBody UserUpdateRequest userUpdateRequest) { ...... } ``` ### 3.4. DELETE 请求 @ DeleteMapping("/users/{userId}")等价于@ RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE) ``` @DeleteMapping("/users/{userId}") public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId){ ...... } ``` ### 3.5. PATCH 请求 一般实际项目中,我们都是 PUT 不够用了之后才用 PATCH 请求去更新数据。 ``` @PatchMapping("/profile") public ResponseEntity updateStudent(@RequestBody StudentUpdateRequest studentUpdateRequest) { studentRepository.updateDetail(studentUpdateRequest); return ResponseEntity.ok().build(); } ``` ## 4. 前后端传值 **掌握前后端传值的正确姿势,是你开始 CRUD 的第一步! ** ### 4.1. `@ PathVariable` 和 `@ RequestParam` `@ PathVariable`用于获取路径参数,`@ RequestParam`用于获取查询参数。 举个简单的例子: ``` @GetMapping("/klasses/{klassId}/teachers") public List<Teacher> getKlassRelatedTeachers( @PathVariable("klassId") Long klassId, @RequestParam(value = "type", required = false) String type ) { ... } ``` 如果我们请求的 url 是:`/klasses/{123456}/teachers?type=web` 那么我们服务获取到的数据就是:`klassId=123456,type=web`。 ### 4.2. @RequestBody 用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。 我用一个简单的例子来给演示一下基本使用! 我们有一个注册的接口: ``` @PostMapping("/sign-up") public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) { userService.save(userRegisterRequest); return ResponseEntity.ok().build(); } ``` `UserRegisterRequest`对象: ``` @Data @AllArgsConstructor @NoArgsConstructor public class UserRegisterRequest { @NotBlank private String userName; @NotBlank private String password; @FullName @NotBlank private String fullName; } ``` 我们发送 post 请求到这个接口,并且 body 携带 JSON 数据: ``` {"userName":"coder","fullName":"shuangkou","password":"123456"} ``` 这样我们的后端就可以直接把 json 格式的数据映射到我们的 UserRegisterRequest 类上。 ![](http://img1.sycdn.imooc.com/5eaa988200014dbf10780338.jpg) 需要注意的是:一个请求方法只可以有一个`@ RequestBody`,但是可以有多个`@ RequestParam`和`@ PathVariable`。 如果你的方法必须要用两个 `@ RequestBody`来接受数据的话,大概率是你的数据库设计或者系统设计出问题了! ## 5. 读取配置信息 很多时候我们需要将一些常用的配置信息比如阿里云 oss、发送短信、微信认证的相关配置信息等等放到配置文件中。 下面我们来看一下 Spring 为我们提供了哪些方式帮助我们从配置文件中读取这些配置信息。 我们的数据源application.yml内容如下:: ``` wuhan2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,但是,我相信一切都会过去!武汉加油!中国加油! my-profile: name: Guide哥 email: koushuangbwcx@163.com library: location: 湖北武汉加油中国加油 books: - name: 天才基本法 description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。 - name: 时间的秩序 description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。 - name: 了不起的我 description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻? ``` ### 5.1. @value(常用) 使用 `@ Value("${property}")` 读取比较简单的配置信息: ``` @Value("${wuhan2020}") String wuhan2020; ``` ### 5.2. @ ConfigurationProperties(常用) 通过`@ ConfigurationProperties`读取配置信息并与 bean 绑定。 ``` @Component @ConfigurationProperties(prefix = "library") class LibraryProperties { @NotEmpty private String location; private List<Book> books; @Setter @Getter @ToString static class Book { String name; String description; } 省略getter/setter ...... } ``` 你可以像使用普通的 Spring bean 一样,将其注入到类中使用。 ### 5.3. PropertySource(不常用) `@ PropertySource`读取指定 properties 文件 ``` @Component @PropertySource("classpath:website.properties") class WebSite { @Value("${url}") private String url; 省略getter/setter ...... } ``` 更多内容请查看我的这篇文章: [《10 分钟搞定 SpringBoot 如何优雅读取配置文件?》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486181&idx=2&sn=10db0ae64ef501f96a5b0dbc4bd78786&chksm=cea2452ef9d5cc384678e456427328600971180a77e40c13936b19369672ca3e342c26e92b50&token=816772476&lang=zh_CN#rd) ## 6. 参数校验 .... 待完善(后面的不常用)这里就不继续写了,可以看看原文。

SpringBoot中view和path同名导致报错

  • 2020-05-17
  • 124
  • SpringBoot
今天帮同学解决了一个由于粗心导致的Bug,特地记录一些,防止自己以后由于粗心导致同样的BUG。 其实特别容易解决,先来看报错的日志: ![](http://image.linkaiblog.top/_2020051709151915SS.png) 这个很明显就是 Controller 层的代有问题,然后在来仔细看一下代码: ![](http://image.linkaiblog.top/_2020051709212121SS.png) 可以看到,就是因为 @ GetMapping 里面的路径名和需要返回的视图名称同名,导致了报错,只要修改一下改成不同名字就可以正常运行了! **:.゚ヽ(。◕‿◕。)ノ゚.:。+゚ 好吧,今天又水了一篇博客~~ (ง •̀_•́)ง (*•̀ㅂ•́)و **

博客时间轴_按时间分类功能实现

  • 2020-05-16
  • 143
  • SpringBoot
今天记录一下博客按时间归档功能的思路以及具体的实现步骤。 先上效果图: ![](http://image.linkaiblog.top/时间轴_2020051610490649SS.png) **主要的解决过程有 2 个:** - 第 1 个是通过 MySQL 的查询语句,统计不同时间段内的博客数量及内容,然后返回数据给前端。 - 第 2 个就是结合前端的 Thymeleaf 模版引擎提供的一些日期格式化工具,根据后端提供的数据,实现博客的按时间归档。 **这里仅仅提供我的一种思路。因为每个人的数据表的结构不太一样,这里只能提供一种思路供大家参考。** **只要思想不滑坡,办法总比困哪多!** ### 通过 MySQL 查询将数据归档 首先,我们存在数据库中的时间是精确到秒的,如下图所示: ![](http://image.linkaiblog.top/时间轴_2020051610431343SS.png) #### (1)通过 year,month等函数将时间中的年,月分离出来 对应的 SQL 语句1: **注意:由于这里需要统计博客的数量,所以不能实现将每条博客记录的具体时间查询出来,这得借助第 2 个查询语句。** ``` select year(javaweb_myblog.blog.createtime) as 'year', month(javaweb_myblog.blog.createtime) as 'month', count(*) as 'count' from javaweb_myblog.blog group by year(javaweb_myblog.blog.createtime) desc, month(javaweb_myblog.blog.createtime) desc ``` 查询结果如下图所示: ![](http://image.linkaiblog.top/时间轴_2020050311253025SS.png) 这样,我们就能获得每个月份下的博客总数有多少,基本实现了按照月份归档。 #### (2)MySQL 查询每条具体的博客,将时间一并查询出来 对应的 SQL 语句2: **注意:这里实际只需要查询每条博客记录的具体时间即可,对应的年份和月份我们都可以在 Thymeleaf 模版中通过具体时间获得年份,月份。当然,我们也可以如下所示,在 SQL 层面查询出年份和月份。** ``` select year(javaweb_myblog.blog.createtime) as 'year', month(javaweb_myblog.blog.createtime) as 'month', javaweb_myblog.blog.createtime as 'time', javaweb_myblog.blog.btitle as 'title', javaweb_myblog.blog.bid as 'bid' from javaweb_myblog.blog order by javaweb_myblog.blog.createtime desc; ``` 查询结果如图所示: ![](http://image.linkaiblog.top/时间轴_202005031113001SS.png) 到这里我们的思路就很明确了: **第1个查询可以将博客按照月份归档,那么第2个查询的结果可以和第1个查询的结果做比较,即在 Thymeleaf 中显示时,如果月份相同,即显示该条记录,表明该条记录属于当前月份下分布的博客。** 那么下面我们给出完整的代码 ### 完整代码展示 代码只给出了关键代码,service和dao层的代码没有贴出来(dao层代码可以参考前面给出的 SQL 语句执行编写);不关键的 html 代码也没有贴出来。 > Controller 层 ``` @GetMapping("/Timeline") public String toTimeline(Model model) { List<Map<String, Object>> listWithCount = blogService.queryTimeLingWithCount(); System.out.println("listWithCount size = " + listWithCount.size()); // 对应着第1个查询语句 List<Map<String, Object>> listWithOutCount = blogService.queryTimeLingWithOutCount(); System.out.println("listWithOutCount size = " + listWithOutCount.size()); // 对应着第2个查询语句 model.addAttribute("listWithCount", listWithCount); model.addAttribute("listWithOutCount", listWithOutCount); return "timeline"; } ``` > 前端HTML页面 ``` <div class="history-date" th:each="itemWithCount:${listWithCount}"> <ul> <h2 class="first"> <a href="#" th:text="${itemWithCount.get('year')} + '年' + ${itemWithCount.get('month')} + '月' + '(' + ${itemWithCount.get('count')} + ')'"></a> </h2> <!-- 关键代码(这个 if 判断是关键):查询1和查询2比较,要求月份和年份相同,这将这条记录展示出来 --> <li class="green" th:each="itemWithOutCount:${listWithOutCount}" th:if="${itemWithOutCount.get('year')} == ${itemWithCount.get('year')} and ${itemWithOutCount.get('month')} == ${itemWithCount.get('month')}"> <h3> [[ ${itemWithOutCount.get('month')} + '月' + ${#dates.day(itemWithOutCount.get('time')) + '日'} ]] <span th:text="${#dates.hour(itemWithOutCount.get('time'))} + '时' + ${#dates.minute(itemWithOutCount.get('time')) + '分'}"></span> </h3> <dl> <dt> <a th:href="@{/article/} + ${itemWithOutCount.get('bid')}" th:text="${itemWithOutCount.get('title')}" target="_blank"></a> </dt> </dl> </li> </ul> </div> ```

七牛云实现图片上传,整合Edit.md

  • 2020-05-11
  • 155
  • SpringBoot
今天一个阿里云的ECS云服务器挂了,CPU积分消耗完了。 因为自己的是开源博客可,把数据库的密码放在了github上,没有对配置文件进行一定的修改,导致前段时间一直被攻击,然后CPU积分消耗完了,速度变得特别慢。 于是换了一个阿里云的轻量级应用服务器,然后之前的图片也失效了。终于知道了数据的重要性,之后要在本地和服务器端都要做好备份工作。 现在干脆利用七牛云的对象存储服务来存储自己博客的图片。同时本地做好备份,服务器端也做好备份。 好了,废话不多说,进入正题。 ---- ## (1)基本的图片上传功能 ### 1. 注册七牛云账号 地址:https://www.qiniu.com/ 注册账号,实名认证之后就可以用了。实名认证很快的,差不多从注册开始不到10分钟就可以搞完。 ### 2. 创建空间 进入控制台,在左侧 “对象存储” 中找到 “空间管理”,新建一个空间。选择 “公开” ![](http://image.linkaiblog.top/image-20200511111134142.png) ### 3. 绑定自己的域名 七牛云会提供一个免费的域名给你,不过只能用 30 天,我们可以使用自己的域名 在控制台左侧 “CND” 中选择 “域名管理”,添加一个自己的域名。之后选择默认的配置即可,然后等待3分钟左右添加成功。 域名尽量原则带绑定带前缀的域名,例如 xxx.baidu.com ,用百度举个例子,因为后面配置 CNAME 是可能会产生冲突 ![](http://image.linkaiblog.top/image-20200511111427001.png) ### 4. 配置 CNAME 添加域名之后,配置CNAME,可以参考给出的文档,文档已经写得特别详细了 ![](http://image.linkaiblog.top/image-20200511111720521.png) 配置 CNAME 文档地址: https://developer.qiniu.com/fusion/kb/1322/how-to-configure-cname-domain-name 由于我的域名使用过 A 记录解析,所以会产生冲突,于是我有重新添加了一个域名: image.linkaiblog.top 在阿里云控制台如下图所示: ![](http://image.linkaiblog.top/image-20200511112203138.png) 配置成功之后在七牛云 “空间管理” 中的 CDN 加速域名的状态会显示为成功 ![](http://image.linkaiblog.top/image-20200511112321786.png) ### 5.之后我们就可以上传文件了 ![](http://image.linkaiblog.top/image-20200511112437984.png) 上传功能之后点击右侧的 “详情”就可以查看图片的信息了 ![](http://image.linkaiblog.top/image-20200511112532129.png) ### 6. 在 Typora 中查看 复制图片的外链,在 Typora 中输入,查看结果如下所示 ![](http://image.linkaiblog.top/image-20200511112807605.png) ## (2)使用 API 在代码中实现 之前都是一些基础的功能,上传图片的话还得手动上传,在 MarkDown 中贴图片链接,我们自然是觉得很麻烦的。 在我们的博客后台中,可以调用七牛云提供的 API,实现图片的上传。 各种SDK地址:https://developer.qiniu.com/sdk#official-sdk 文档地址:https://developer.qiniu.com/kodo/sdk/1239/java#install-by-maven ### 1. 添加 Maven 依赖 ![](http://image.linkaiblog.top/image-20200511113722866.png) 选择一个版本,去仓库看一下,就用最新的吧 7.2.29 ![](http://image.linkaiblog.top/image-20200511113812825.png) 记得还要添加第3方依赖,可能用到 ![](http://image.linkaiblog.top/image-20200511133533502.png) ### 2. 编写 Java 代码 - 文档中已经很详细了,这里我们演示一下本地的文件上传 ![](http://image.linkaiblog.top/image-20200511125008496.png) - 编写对应的测试类,注意,有很多同名的类名,需要使用 com.qiniu.storage 包下面的类 ``` package com.linkai.myblog; import com.google.gson.Gson; import com.qiniu.common.QiniuException; import com.qiniu.http.Response; import com.qiniu.storage.Configuration; import com.qiniu.storage.Region; import com.qiniu.storage.UploadManager; import com.qiniu.storage.model.DefaultPutRet; import com.qiniu.util.Auth; import org.junit.jupiter.api.Test; @SpringBootTest class MyblogApplicationTests { @Test void testUpload() { // 注意是在 :com.qiniu.storage 包下面的 Configuration cfg = new Configuration(Region.region2()); // 由于我们选择的是华南的机房,所以这里调用 region2() UploadManager uploadManager = new UploadManager(cfg); String accessKey = "your access key"; String secretKey = "your secret key"; String bucket = "your bucket name"; // 填写我们的存储空间的名称 //如果是Windows情况下,格式是 D:\\qiniu\\test.png String localFilePath = "D:\\temp\\test.png"; //默认不指定key的情况下,以文件内容的hash值作为文件名 String key = null; Auth auth = Auth.create(accessKey, secretKey); String upToken = auth.uploadToken(bucket); // com.qiniu.http.Response; try { Response response = uploadManager.put(localFilePath, key, upToken); // 解析上传成功的结果 DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); System.out.println(putRet.key); System.out.println(putRet.hash); } catch (QiniuException e) { Response r = e.response; System.err.println(r.toString()); try { System.err.println(r.bodyString()); } catch (QiniuException ex) { ex.printStackTrace(); } } } } ``` - 特别注意包名,要导入正确的包 ![](http://image.linkaiblog.top/image-20200511125627691.png) ### 3. 启动测试类 测试成功,打印了我们的文件内容的hash值 ![](http://image.linkaiblog.top/image-20200511125721039.png) ### 4. 回到七牛云的控制台查看效果 ![](http://image.linkaiblog.top/image-20200511124905932.png) ## (3)整合 Edit.md 由于我们的博客后台使用的是 Edit.md 作为 Markdown 编辑器,之前上传文件都是直接保存到服务器上的,用了七牛云的图床之后,对代码稍微修改一下。 - 由于在 Controller 中我们传入的参数是 MultipartFile 对象,获取文件路径比较麻烦,所以我们可以直接用 MultipartFile 对象的 getBytes() 方法获取字节数组,利用字节数组上传 ### 1. 具体代码 ``` package com.linkai.myblog.controller; import com.alibaba.fastjson.JSONObject; import com.google.gson.Gson; import com.qiniu.common.QiniuException; import com.qiniu.http.Response; import com.qiniu.storage.Configuration; import com.qiniu.storage.Region; import com.qiniu.storage.UploadManager; import com.qiniu.storage.model.DefaultPutRet; import com.qiniu.util.Auth; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.commons.CommonsMultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; @RestController public class FileController { @RequestMapping("/uploadQNY") public JSONObject fileUploadQNY(@RequestParam("editormd-image-file") MultipartFile file, HttpServletRequest request) { // 注意是在 :com.qiniu.storage 包下面的 Configuration cfg = new Configuration(Region.region2()); // 由于我们选择的是华南的机房,所以这里调用 region2() UploadManager uploadManager = new UploadManager(cfg); String accessKey = "your access key"; String secretKey = "your secret key"; String bucket = "jacklin-blog"; // 填写我们的存储空间的名称 //默认不指定key的情况下,以文件内容的hash值作为文件名 // 我们这里的 key 可以指定为本地文件的文件名 String key = file.getOriginalFilename(); // Edit.md 构造返回的 Json 字符串,这里的 url 就设置成 文件的名称即可 JSONObject jsonObject = new JSONObject(); jsonObject.put("url", "http://image.linkaiblog.top/" + key); jsonObject.put("message", "upload success!"); //如果文件名为空,直接回到返回 if ("".equals(key)){ // 0 表示上传失败 jsonObject.put("success", 0); return jsonObject; } try { byte[] uploadButes = file.getBytes(); Auth auth = Auth.create(accessKey, secretKey); String upToken = auth.uploadToken(bucket); Response response = uploadManager.put(uploadButes, key, upToken); // 解析上传成功的结果 --》 需要用到 com.google.gson.Gson DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class); System.out.println("文件名为:" + putRet.key); System.out.println("文件内容的hash值为" + putRet.hash); } catch (QiniuException ex) { Response r = ex.response; System.err.println(r.toString()); try { System.err.println(r.bodyString()); } catch (QiniuException ex2) { //ignore } } catch (IOException e) { e.printStackTrace(); } // 1 表示上传成功 jsonObject.put("success", 1); return jsonObject; } ``` ### 2. 测试上传 ![](http://image.linkaiblog.top/image-20200511134309964.png) ![](http://image.linkaiblog.top/image-20200511134356628.png) 回到控制台查看效果 ![](http://image.linkaiblog.top/image-20200511124905932.png)

RabbitMQ的安装(Docker),基本介绍以及整合SpringBoot

  • 2020-05-05
  • 140
  • SpringBoot
## RabbitMQ的安装,通过Docker安装 #####(1)去到 Docker Hub官网,搜索 RabbitMQ 尽量安装带 management 的,我这里安装的是 3.8 版本的 ![](/upload/2020-5-5 134149.png) 执行安装 `docker pull rabbitmq:3.8-management` ![](/upload/2020-4-30 103308.png) #####(2)安装完成之后启动 RabbitMQ 启动 RabbitMQ `docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq fa535c4b51fe` - -d 表示后台启动 - -p 表示端口映射 - 5672 是 RabbitMQ 的默认端口,我们需要使用这个端口连接上 RabbitMQ - 15672 是 RabbitMQ 的UI界面的端口,我们连接之后可以进行可视化操作 - --name 表示为我们的容器起一个名字 - 后面的一串字符是我们的镜像 ID,这里要换成自己的镜像ID,通过 `docker iamges` 命令可以查看 emm~~~ 忘记截图了 #####(3)连接图形化界面 输入地址加15672端口号,进入图形界面 - 记得开放阿里云安全组 - 开放15672端口和5672端口 ---》 `firewall-cmd --add-port=6379/tcp` ## RabbitMQ 的基本介绍 #### 1. 消息服务 (1)JMS(Java Message Service) Java消息服务 - 基于JVM消息代理的规范,ActiveMQ,HornetMQ是JMS的实现 (2)AMQP(Advanced Message Queuing Protocol) - 高级消息队列协议,也是一个消息代理的规范,兼容JMS - RabbitMQ是AMQP的实现 #### 2. RabbitMQ 简介 **RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。** ##### 核心概念 (1)Message 消息,有消息头和消息体组成 (2)Publisher 消息的生产者,也是一个向交换器发布消息的客户端应用程序。 (3)Exchange 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。 (4)Queue 消息队列,用来保存消息直到发送给消费者。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。 (5)Binding 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。 (6)Connection 网络连接,比如一个TCP连接。 (7)Channel 信道,多路复用连接中的一条独立的双向数据流通道。 (8)Consumer 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。 (9)Virtual Host 虚拟主机,表示一批交换器、消息队列和相关对象。 (10)Broker 表示消息队列服务器实体 **关于这些概念的基本示意图: ** ![](/upload/消息队列图示.png) ## SpringBoot整合RabbitMQ #### 1. 创建工程,导入对应的依赖 如果是新的工程,在初始化时选择 Spring for RabbitMQ ![](/upload/2020-5-5 141337.png) 如果是旧的工程,这添加依赖 可以在 SpringBoot 的官方文档中查找 ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> ``` #### 2. 分析自动配置类,进行相关的配置 (1)找到 RabbitAutoConfiguration 类,分析一下 > rabbitConnectionFactory 方法,通过 RabbitProperties 里面的配置,来创建一个 RabbitMQ 的连接工厂。 所以我们可以在 application.yaml 中进行如下配置 ``` spring: rabbitmq: host: 39.98.86.223 username: guest password: guest ``` > 在 RabbitAutoConfiguration 还配置了一个 RabbitTemplate 放入容器中,我们可以通过 RabbitTemplate 来给 RabbitMQ 发送和接收消息。和之前使用的 RedisTemplate,JDBCTemplate 类似 > 还往容器中放置了一个 AmqpAdmin,用来创建和删除 Queue,Exchange,Binding