Fork me on GitHub

JackLin的博客

当前位置:首页 > 标签

SpringBoot 14 Spring 2 SpringMVC 3 MyBatis 2 Linux 4 阿里云 13 宝塔 1 Docker 3 ElasticSearch 2 Redis 4 Shiro 0 Dubbo 0 Swagger 0 Thymeleaf 6 数据库 11 MySQL 11 外键 2 Gradle 1 Test 0 Tomcat 1 JavaWeb 7 Ajax 1 注解 3 css 2 报错 3 多数据源 1 Java基础 1 源码 2 Servlet 1 JSP 1 环境搭建 8 RabbitMQ 1 七牛云 1 Edit.md 1 图像识别 4 英语 2 Zookeeper 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)

阿里云AI训练营_Day04_车辆检测系统

  • 2020-06-08
  • 172
  • 阿里巴巴
## 项目介绍 参加阿里云AI训练营的第4天,完成一个车辆检测系统 主要思路:用户上传身份证和受损车辆图片,识别结果返回前端,同时将数据存入数据库。 ## 项目用到文档地址 阿里云达摩院视觉开放平台:https://vision.aliyun.com/ 阿里云视觉开放平台 “车辆损伤识别” 地址:https://help.aliyun.com/document_detail/155002.html?spm=a2c4g.11174283.6.755.77e06bdfN9iMtt ## 项目模块1-各种识别服务 ### (1)说明 一开始的设想是用到身份证识别和车辆识别2个服务,但是发现同时导入 2 个依赖之后,有一些 jar 包会冲突,导致身份证识别的部分由于缺少一些 jar 包而不能正常运行。最后只用到了机动车识别和车辆损伤识别2个服务。 ### (2)导入项目依赖 这里的依赖我们使用较早的版本 ```xml <!-- 内容识别对应依赖 --> <!-- https://mvnrepository.com/artifact/com.aliyun/objectdet --> <dependency> <groupId>com.aliyun</groupId> <artifactId>objectdet</artifactId> <version>0.0.5</version> </dependency> <!-- 图片识别 --> <dependency> <groupId>com.aliyun</groupId> <artifactId>ocr</artifactId> <version>1.0.3</version> </dependency> ``` 之后同样要开通 “目标检测服务” 才能正常运行。 ### (3)创建 Client,Config类 虽然身份证识别部分会报错,但是这里依旧吧身份证识别部分的代码贴出来 > CraService.java ```java @Value("${accessKeyId}") private String accessKeyId; @Value("${accessKeySecret}") private String accessKeySecret; // "内容识别" --> 导入这个类:import com.aliyun.objectdet20191230.Client; private com.aliyun.objectdet.Client objectClient; private com.aliyun.ocr.Client ocrClient; private RuntimeObject runtimeObject; private RuntimeOptions runTimeOperations; @PostConstruct public void initClient() throws Exception { Config objectConfig = new Config(); objectConfig.type = "access_key"; objectConfig.regionId = "cn-shanghai"; objectConfig.accessKeyId = accessKeyId; objectConfig.accessKeySecret = accessKeySecret; // 注意这里的域名,我们是 “内容识别” 服务,所以开头是 objectdet= objectConfig.endpoint = "objectdet.cn-shanghai.aliyuncs.com"; objectClient = new com.aliyun.objectdet.Client(objectConfig); runtimeObject = new RuntimeObject(); com.aliyun.ocr.models.Config ocrConfig = new com.aliyun.ocr.models.Config(); ocrConfig.type = "access_key"; ocrConfig.regionId = "cn-shanghai"; ocrConfig.accessKeyId = accessKeyId; ocrConfig.accessKeySecret = accessKeySecret; // 注意这里的域名,我们是 “内容识别” 服务,所以开头是 objectdet= ocrConfig.endpoint = "ocr.cn-shanghai.aliyuncs.com"; ocrClient = new com.aliyun.ocr.Client(ocrConfig); runTimeOperations = new RuntimeOptions(); } ``` 需要注意的是两个 Client 是不同的包下面的 Client 类,一个是 “内容识别” 的,另一个是 “图像识别” 的。 ### (4)调用关键 API 这里原本调用了3个服务,分别是 “身份证识别”,“机动车识别”,“车辆受损识别”,关键代码如下: ```java // “机动车识别” public String myDetectVehicle(String filePath) throws Exception { DetectVehicleAdvanceRequest request = new DetectVehicleAdvanceRequest(); request.imageURLObject = new FileInputStream(filePath); DetectVehicleResponse response = objectClient.detectVehicleAdvance(request, runtimeObject); String result = null; // 发现这是一个内部类 for (DetectVehicleResponse.DetectVehicleResponseDataDetectObjectInfoList item:response.data.detectObjectInfoList ) { System.out.println(item.type); System.out.println(item.score); if ("vehicle".equals(item.type)) { System.out.println("检测成功!是机动车"); } else { System.out.println("该图片不是机动车!"); } result = item.type; } return result; } // “车辆损伤识别” public String myRecognizeVehicle(String filePath) throws Exception { // 使用 xxxAdvanceRequest,支持本地图片上传 RecognizeVehicleDamageAdvanceRequest request = new RecognizeVehicleDamageAdvanceRequest(); request.imageURLObject = new FileInputStream(filePath); // 识别 “车辆损伤” RecognizeVehicleDamageResponse response = objectClient.recognizeVehicleDamageAdvance(request, runtimeObject); return getHurtResult(response.data.elements); } // “身份证识别” --------> public String MyRecognizeIdCard(String filePath, String side) throws Exception { RecognizeIdentityCardAdvanceRequest request = new RecognizeIdentityCardAdvanceRequest(); request.imageURLObject = Files.newInputStream(Paths.get(filePath)); request.side = side; RecognizeIdentityCardResponse response = ocrClient.recognizeIdentityCardAdvance(request, runTimeOperations); if ("face".equals(side)) { return JSON.toJSONString(response.data.frontResult); } else { return ""; } } ``` ### (5) 车辆损伤补充代码 看文档我们知道,由于车辆损伤有很多种情况,所以我们需要对数据进行一定的处理,思路是通过一个 switch~case 语句区分各种损伤的具体情况。 ![](http://image.linkaiblog.top/image-20200608180242183.png) > 具体代码: ```java // 对 “车辆损伤” 识别出来的数据进行一定的处理,方便前台展示 public String getHurtResult(RecognizeVehicleDamageResponse.RecognizeVehicleDamageResponseDataElements[] items) { StringBuffer type = new StringBuffer("检测到的车辆损伤为:"); for (RecognizeVehicleDamageResponse.RecognizeVehicleDamageResponseDataElements item:items ) { switch (item.type) { case "1": type.append("轻微刮擦 "); break; case "2": type.append("重度刮擦 "); break; case "3": type.append("轻度变形 "); break; case "4": type.append("中度变形 "); break; case "5": type.append("重度变形 "); break; case "6": type.append("crack破损孔洞 "); break; case "7": type.append("翼子板和大灯缝隙 "); break; case "8": type.append("翼子板保险杠缝隙 "); break; case "9": type.append("大灯轻微刮擦 "); break; case "10": type.append("大灯重度刮擦 "); break; case "11": type.append("大灯破损 "); break; case "12": type.append("后视镜轻微刮擦 "); break; case "13": type.append("后视镜玻璃破损 "); break; case "14": type.append("后视镜脱落 "); break; case "15": type.append("挡风玻璃破损 "); break; } } return type.toString(); } ``` ## 项目模块2-图片上传服务,及数据库操作 这部分做的比较简易,没有美化样式,能看就行~就是一个简单的 from 表单 > test.html ```html <form th:action="@{/upload}" enctype="multipart/form-data" method="post"> 身份证:<input type="file" name="identityfile"> 受损车辆:<input type="file" name="carfile"> <input type="submit" value="upload"> </form> ``` ---- 图片上传对应的 Controller,上传完成之后,同时进行识别,然后在将识别输入存入数据库中 > CarController.java ```java @RequestMapping("/upload") public String fileUpload(@RequestParam("carfile") MultipartFile carFile, @RequestParam("identityfile") MultipartFile identityFile, Model model) throws IOException { // 1. 上传图片 ----》 “身份证” String identityPath = aiCarService.fileUpload(identityFile); // 2. 上传图片 -----》 “受损车辆” String carPath = aiCarService.fileUpload(carFile); // 3. 调用 Service 层分析图片 String face = null; String carStr = null; String carStr2 = null; try { // 3.1 身份证识别 ---> 只需要识别正面 // face = carService.MyRecognizeIdCard(identityPath , "face"); // 3.2 机动车检查 carStr = aiCarService.myDetectVehicle(carPath); // 3.3 车辆损伤识别 carStr2 = aiCarService.myRecognizeVehicle(carPath); } catch (Exception e) { e.printStackTrace(); } // 4. 保存数据到数据库中 Car car = new Car(null, carStr, carStr2); carService.insert(car); // 4. 将图片路径放入 Model 中 String carFileName = carFile.getOriginalFilename(); String identityFileName = identityFile.getOriginalFilename(); model.addAttribute("carfilename", "/upload/" + carFileName); model.addAttribute("identityfilename", "/upload/" + identityFileName); model.addAttribute("face", face); if ("vehicle".equals(carStr)) { model.addAttribute("carStr", "检测成功!是机动车"); } else { model.addAttribute("carStr", "您上传的图片不是机动车,请重新上传!!"); } model.addAttribute("carStr2", carStr2); return "result"; } ``` 对应的 Service > CarService.java ```java /** * @Description: 图片上传服务 * @Param: [file] * @return: void * @Author: 林凯 * @Date: 2020/6/8 */ public String fileUpload(MultipartFile file) { // 1. 获取文件名 String uploadFileName = file.getOriginalFilename(); // 2. 上传路径保存设置 UUID String path = "src/main/resources/static/upload"; // 2.1 如果路径不存在,创建一个 File realPath = new File(path); if (!realPath.exists()){ realPath.mkdir(); } // 3. 上传文件,并保存 InputStream is = null; //文件输入流 OutputStream os = null; try { is = file.getInputStream(); os = new FileOutputStream(new File(realPath, uploadFileName)); 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 path + "\\" + uploadFileName; } ``` 返回结果给前端,前端的处理部分 > result.html ```html <img th:src="${carfilename}" alt=""> <img th:src="${identityfilename}" alt=""> <p th:text="${carStr}"></p> <p th:text="${carStr2}"></p> ``` ## 测试效果 首先进入上传图片页面,并上传2张图片 ![](http://image.linkaiblog.top/image-20200608185634408.png) 然后点击 upload 上传并查看结果: ![](http://image.linkaiblog.top/image-20200608190502181.png) 数据库里面的结果: ![](http://image.linkaiblog.top/image-20200608190901927.png) ## 阿里云高校计划 最后在贴一张阿里云的广告:”阿里云高校计划“,快来加入我们吧! ![](http://image.linkaiblog.top/image-20200606222043666.png)

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

  • 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> ```

SpringBoot中JS代码获取Model中的值

  • 2020-05-01
  • 133
  • SpringBoot
今天总结一下如何在前端的HTML页面中利用JS代码获取Model中的值,主要有2种方法。 ### 利用隐藏域 我们可以先利用Thymeleaf模版引擎将Model里面的值放在一个div标签中,然后设置display属性为hidden ### 直接在JS中利用表达式

SpringBoot中Controller返回路径问题

  • 2020-04-15
  • 138
  • SpringBoot
### SpringBoot是如何根据路径寻址文件的 要想知道Controller中的返回路径究竟如何填写,我们就必须知道SpringBoot是如何解析我们写的路径的。 关于这部分内容,我们得首先从SpringMVC开始说起。 #### (1)SpringMVC中路径问题 熟悉SpringMVC的小伙伴都知道SpringMVC的基本访问流程,如何不熟悉的话可以参考我的这篇博客:xxx #### (2)SpringBoot中解析路径 和SpringMVC类似,我们也是需要找到他的视图解析器相关的配置文件。 `WebMvcAutoConfiguration` `ResourceProperties` 这类配置文件再SpringBoot中一般是xxxProperties.java 的格式;所以我们找到 `ThymeleafProperties.java` 在这个类里面我们可以很清楚的看到如下代码: ``` @ConfigurationProperties(prefix = "spring.thymeleaf") public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; // 剩余代码... } ``` 是不是有种似曾相识的感觉,这里的 prefix = "spring.thymeleaf" 表示我们在 application.yaml 文件中进行相关的配置,需要以 spring.thymeleaf 开头。 接下来有3个常量: - DEFAULT_ENCODING 设置了一下Thymeleaf的默认编码 - DEFAULT_PREFIX 设置了解析时的前缀 - DEFAULT_SUFFIX 设置了解析时的后缀 就是说如果我们在Controller中直接返回一个字符串,例如 return "main"; 那么SpringBoot就会去 templates/static 目录下面去寻找以 main 开头, .html 结尾的文件,也就是去寻找 main.html 文件。 #### (3)关于重定向 如果在Controller中需要重定向,那么在开头尽量添加上 / ,避免出现不必要的麻烦,因为 / 代码根目录。 如果不加的话,而且在Controller类上面又添加l @ ResponeBody 注解,如下所示,可能出现资源找不到情况。 ``` @Controller @RequestMapping("/admin") public class BlogController { @RequestMapping("/addBlog") public String addBlog() { return "redirect:/admin/Blog"; } } ``` - 如果是 `return "redirect:/admin/Blog";` 那么地址栏将会显示 http://localhost/admin/Blog - 如果是 `return "redirect:admin/Blog";` 那么地址栏将会显示 http://localhost/admin/admin/Blog 前面多了一个 amdin,就是因为我们在类上面添加了 @ RequestMapping("/admin") 造成的原因 #### (4)关于部署到服务器上(本地可以运行,服务器上报500错误) ##### 1. 部署到服务器上的时候, return 语句前面不需要加上 /,否则会报错 ``` @RequestMapping("/Blog") public String blog(Model model) { // 逻辑代码 return "main/blog"; } ``` - 如上面代码所示,直接返回 "main/blog" 就行了,如果返回 "/main/blog" 就或报错 ##### 2. 需要严格的遵守Thymeleaf的相关规范 **例如,使用Thymeleaf抽取公共页面时,前面也不能加 / ** ``` <!-- 文章页面:头部导航栏--> <div th:replace="~{common/commonsfile::article-topbar}"></div> ``` - 第一个common前面不能加 /

Thymeleaf的基本使用

  • 2020-04-14
  • 150
  • Thymeleaf
### Thymeleaf 的简单介绍 - **Thymeleaf** 简单的来说就是一个**模版引擎**,和 JSP 有很大的相似之处;JSP的绝大部分功能 Thymeleaf都有,同时Thymeleaf还进行了一定的扩展;可以说,Thymeleaf的功能更加强大。 - 模版引擎通俗的来讲就是用来渲染数据的,将后端传递过来的数据展示出来;也就是我们所说的“套模板”。 - 只不过JSP主要用于SSM框架当中,而Thymeleaf主要是结合SpringBoot一起使用。 ### Thymeleaf的基本使用 #### (1)导入对应的依赖 首先,和JSP有点不同,我们不需要编写 .jsp 文件,而是编写 .html 文件;只不过需要在 .html 文件的开头添加上 Thymeleaf 的依赖;同时在 pom.xml 文件中也用添加对应的依赖。 > pom.xml ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> ``` > xxx.html 文件 ``` <!DOCTYPE html> <!-- 使用 Thymeleaf 模版引擎--> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head></head> <body></body ``` #### (2)thymeleaf 语法简单介绍 详细语法可以参考官方文档: 这里只给出经常需要使用的语法 ##### 1. th:text="${}" ##### 2. th:src="@{}" ##### 3. ### Thymeleaf实战技巧 #### 1. 日期的格式化 我们可以利用 Thymeleaf 里面的内置对象来实现 ``` <div> 发布日期:<span th:text="${#dates.format(blog.getCreatetime(), 'yyyy-MM-dd')}"></span> </div> ``` - 这里的 blog.getCreatetime() 获得的是一个 Date 对象,‘yyyy-MM-dd’是格式化字符串,和Java中的格式化字符串的语法类型 - ‘yyyy-MM-dd HH : mm : ss’ 就是精确到秒 #### 2.foreach 遍历 当后台管理需要展示表格时,foreach可以很方便的解决问题 ``` <!-- 特别注意,放在表格里面的标签可能有多个,所以不能使用 id 属性--> <tr class="table-color table-warning" th:each="blog,blogState:${blogs}"> <th scope="row" th:text="${blogState.index + 1}"></th> <td th:text="${blog.getBtitle()}"></td> <td th:text="${blog.getType().getTypename()}"></td> </tr> ``` - 其中 blogs 是存放在 Model 中的 List 集合,blog表示从List集合中遍历出来的元素。 - blogSatte表示遍历过程中的下标,从0开始。 #### 3. if 条件判断 例如前端需要显示一些错误信息是,可以通过 if 判断条件是否满足进而显示或隐藏标签 ``` <!-- 不为空才显示这个标签 --> <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p> ``` #### 4. 三目运算符 有时候使用if条件判断显得太过臃肿,而已有点场景并不适合使用,我们可以使用三目运算符来代替 ``` <input type="checkbox" class="my_checkbox" th:checked="${blog.getCommentabled()} == 1 ? false : true"> ``` - 如果 blog.getCommentabled() 等于1的话,checked就位false,否则就位true #### 5. a标签的地址使用 RestFul 风格 当a标签的链接地址使用RestFul风格时,thymeleaf也可以很方便的处理,只不过这时需要拼接。 ``` <!-- 利用 a 标签发送请求,结合 RestFul 风格--> <a th:href="@{/admin/deleteBlog/} + ${blog.getBidToString()}">删除</a> ``` - **注:这样使用,在IDEA中语法检查时虽然会报红,但是实际运行的时候并不会出错** #### 6. 页面国际化时的使用 废话不多说,直接上代码 ``` <a class="btn btn-sm" th:href="@{/index.html(language = 'zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index.html(language = 'en_US')}">English</a> ``` #### 7. 抽取页面的公共部分 使用 Thymeleaf 将一些页面的公共部分抽取出来,可以减少大量冗余代码。 ``` // 需要抽取的部分 <div class="title" th:fragment="article-topbar"> // 具体代码逻辑 </div> ``` ``` // 将抽取的部分插入到这里 <div th:replace="~{common/commonsfile::article-topbar}"></div> ``` - 注意:被抽取的部分的代码 commonsfile.html 位于 static/common 目录下面 #### 8.引入js,css文件的src属性的路径问题 引入js,css文件需要使用 th:src=“@{}” 来引入,需要注意路径问题。是从 static 目录下面开始的,**开头要写 /** ,不写 / 的话,由于路径的改变,可能会报错;然后接着写该目录下对应的文件夹 ``` <link rel="stylesheet" th:href="@{/css/article-title.css}"> <script th:src="@{/js/jquery-3.4.1.js}"></script> ``` 目录结构: - templates/static/css/ariticle-title.css - templates/static/js/jquery-3.4.1.js #### 9. 关于css文件中的 background-img 路径问题 路径值需要写相对路径就行了,能够自动寻找到对应的图片的 ``` #shape{ background: url("../image/rocket.png") no-repeat scroll 0 0 transparent; } ```