当前位置:首页 > 标签
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
- 217
- SpringBoot
## 项目介绍
参加阿里云AI训练营的第5天,也是最后一天了。今天是创意日,想做什么就做什么,没有题目约束。
那么我打算做一个简单的识别图片中的文字的应用,并部署上线,以后也可能经常会用到。
## 项目用到的文档地址
阿里云达摩院视觉开放平台:https://vision.aliyun.com/
阿里云视觉开放平台 “通用识别” 地址:https://help.aliyun.com/document_detail/151896.html?spm=a2c4g.11186623.6.620.44da1ded5yuZbF
## 项目开始
### (1)说明
经过前面几天的训练,自我感觉良好。已经对阿里云的视觉开放平台比较熟悉了,也熟悉使用提供的API的一些基础的步骤,感觉就是这些套路。
那么就不啰嗦了,直接上代码。由于只用到了一个场景,所以代码也比较简单,记得测试前要到阿里云开启 “文字识别服务” 哦!

### (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 里面,我们从这里面获取数据即可。

同时,经过测试,文字识别的过程是一行一行识别的,每行识别出来的文字都成为封装在数组的一个元素里面,所以我们定义一个 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>
```
## 测试
先进入主页面,选择图片并点击上传按钮:

识别出来的结果:

## 阿里云高校计划
最后在贴一张阿里云的广告:”阿里云高校计划“,快来加入我们吧!

阿里云AI训练营_Day04_车辆检测系统
- 2020-06-08
- 203
- 阿里巴巴
## 项目介绍
参加阿里云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 语句区分各种损伤的具体情况。

> 具体代码:
```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张图片

然后点击 upload 上传并查看结果:

数据库里面的结果:

## 阿里云高校计划
最后在贴一张阿里云的广告:”阿里云高校计划“,快来加入我们吧!

博客时间轴_按时间分类功能实现
- 2020-05-16
- 176
- SpringBoot
今天记录一下博客按时间归档功能的思路以及具体的实现步骤。
先上效果图:

**主要的解决过程有 2 个:**
- 第 1 个是通过 MySQL 的查询语句,统计不同时间段内的博客数量及内容,然后返回数据给前端。
- 第 2 个就是结合前端的 Thymeleaf 模版引擎提供的一些日期格式化工具,根据后端提供的数据,实现博客的按时间归档。
**这里仅仅提供我的一种思路。因为每个人的数据表的结构不太一样,这里只能提供一种思路供大家参考。**
**只要思想不滑坡,办法总比困哪多!**
### 通过 MySQL 查询将数据归档
首先,我们存在数据库中的时间是精确到秒的,如下图所示:

#### (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
```
查询结果如下图所示:

这样,我们就能获得每个月份下的博客总数有多少,基本实现了按照月份归档。
#### (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;
```
查询结果如图所示:

到这里我们的思路就很明确了:
**第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
- 166
- SpringBoot
今天总结一下如何在前端的HTML页面中利用JS代码获取Model中的值,主要有2种方法。
### 利用隐藏域
我们可以先利用Thymeleaf模版引擎将Model里面的值放在一个div标签中,然后设置display属性为hidden
### 直接在JS中利用表达式
SpringBoot中Controller返回路径问题
- 2020-04-15
- 173
- 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
- 187
- 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;
}
```