目录
1.Springboot整合Thyemleaf+EasyExcel 步骤
2.1.1 补充:@RestController页面跳转方法
最近由于工作需要,实现从excel文件读取数据并用静态页面展示。(前端的html页面迟迟不发过来,只能自己动手写页面展示了,虽然现在都是前后端分离展示,但是对于好多后端开发刚入门的是一个难点,所以就考虑使用Thymeleaf模板引擎实现页面数据展示。
Thymeleaf是一款现代的服务器端 Java 模板引擎,可以轻松与SpringMvc、springBoot等web框架进行集成,适用于 Web 和独立环境开发。与jsp、Velocity、FreeMaker功能类似,thymeleaf可以通过thymeleaf标签渲染处理数据用以展示给用户。
1.Springboot整合Thyemleaf+EasyExcel 步骤
1.1 pom文件引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.7.0</version>
</dependency>
<!-- lombok依赖包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<scope>provided</scope>
</dependency>
<!--swagger依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--swagger-ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- easyexcel 依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
这里有一个很大的坑,自己在这里花了很长时间来解决jar包冲突问题。
- spring-boot-starter-parent在导入这个依赖后,必须要导入spring-boot-starter-web这个依赖。不然会导致springboot项目启动后立即停止的问题。
- spring-boot-starter-thymeleaf的这个依赖版本建议与spring-boot-starter-parent的版本一致,不然可能会导致thymeleaf加载不上。
1.2 yml文件配置
server:
port: 8080
// 解决springboot2.7与swagger2冲突问题
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
thymeleaf:
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
mode: HTML5
doctor:
fileName: "C:\\\\Users\\\\admin\\\\Desktop\\\\Api\\\\医师节数据样例.xlsx"
1.3 config配置类
1.3.1 Swagger2配置类
@Configuration
@EnableSwagger2
public class Swagger2ApiConfig {
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 指定扫描的包路径来定义指定要建立API的目录。
* @return
*/
@Bean
public Docket coreApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(adminApiInfo())
.groupName("Api-wx")
.select()
// 用于指定扫描哪个包下的接口
.apis(RequestHandlerSelectors.basePackage("com.yif.controller"))
// 选择所有的API,如果你想只为部分API生成文档,可以配置这里
.paths(PathSelectors.any())
.build();
}
private ApiInfo adminApiInfo(){
return new ApiInfoBuilder()
.title("wx--api文档")
.description("wx-api接口描述")
.version("1.0")
.contact(new Contact("凡仔","https://github.com/hszyf2051/","151900983@qq.com"))
.build();
}
}
1.3.2 前端跨域问题解决
@Component
public class CrossFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) servletResponse;
res.addHeader("Access-Control-Allow-Credentials", "true");
res.addHeader("Access-Control-Allow-Origin", "*");
//*代表所有网站均无拦截,也可设置自己相应的域名
res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-CAF-Authorization-Token,sessionToken,X-TOKEN");
if (((HttpServletRequest) servletRequest).getMethod().equals("OPTIONS")) {
servletResponse.getWriter().println("ok");
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
2. 后端代码编写
2.1 Controller层
@Controller
public class DoctorController {
@Autowired
private IDoctorService doctorService;
/**
* 查找所有医生
* @param model
* @return
*/
@RequestMapping("/getDoctors")
public String getDoctors(Model model) {
List<Doctor> doctors = doctorService.readDoctors();
model.addAttribute("doctors",doctors);
return "index";
}
/**
* 根据id查找对应的医生
* @param model
* @param id
* @return
*/
@GetMapping("/findDoctorById")
public String findDoctorById(Model model,@RequestParam String id) {
List<Doctor> doctors = doctorService.findDoctorById(id);
model.addAttribute("doctors",doctors);
return "index";
}
}
注意这里的@Controller注解不能写成@RestController,不然会导致页面直接返回index。
return "index" 填写的是resources/templates/下的html页面。必须与页面名字相一致。
2.1.1 补充:@RestController页面跳转方法
若只用@RestController注解,则需要将方法的类型改为ModelAndView。页面的返回路径前不可以加/,不然会无法解析路径,返回字符串!(本人在这里绕了很久)例如"/User",切记不能这么写
@GetMapping("/findUserById")
public ModelAndView findDoctorById(Model model,@RequestParam String id) {
List<User> userList = userService.findUserById(id);
model.addAttribute("users",userList);
// 返回值类型也必须是ModelAndView!!!
return new ModelAndView("User");
}
2.2 service层
@Slf4j
@Service
public class DoctorServiceImpl implements IDoctorService {
/**
* 从yml中读取文件路径
*/
@Value("${doctor.fileName}")
private String fileName;
@Override
public List<Doctor> readDoctors() {
// 创建一个数据格式来承装读取到数据
Class<Doctor> head = Doctor.class;
// 创建ExcelReader对象
List<Doctor> doctorList = new ArrayList<>();
ExcelReader excelReader = EasyExcel.read(fileName, head, new AnalysisEventListener<Doctor>() {
@Override
public void invoke(Doctor doctor, AnalysisContext analysisContext) {
if (doctor.getHealing() == null) {
// 住院患者治疗量
doctor.setHealing(0);
}
if (doctor.getBirthOperations() == null) {
// 生日当天手术量
doctor.setBirthOperations(0);
}
if (doctor.getBirthVisits() == null) {
// 生日当天看诊量
doctor.setBirthVisits(0);
}
doctorList.add(doctor);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}).build();
// 创建sheet对象,并读取Excel的第1个sheet(下标从0开始)
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
// 关闭流操作,在读取文件时会创建临时文件,如果不关闭,磁盘爆掉
excelReader.finish();
log.info(String.valueOf(doctorList));
return doctorList;
}
@Override
public List<Doctor> findDoctorById(String id) {
// 创建一个数据格式来承装读取到数据
Class<Doctor> head = Doctor.class;
// 创建ExcelReader对象
List<Doctor> doctorList = new ArrayList<>();
ExcelReader excelReader = EasyExcel.read(fileName, head, new AnalysisEventListener<Doctor>() {
@Override
public void invoke(Doctor doctor, AnalysisContext analysisContext) {
if (doctor.getId().equals(id)) {
if (doctor.getHealing() == null) {
// 住院患者治疗量
doctor.setHealing(0);
}
if (doctor.getBirthOperations() == null) {
// 生日当天手术量
doctor.setBirthOperations(0);
}
if (doctor.getBirthVisits() == null) {
// 生日当天看诊量
doctor.setBirthVisits(0);
}
doctorList.add(doctor);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if (doctorList.size() == 0) {
log.info("该企业账号:"+ id +",查无此人");
}
}
}).build();
// 创建sheet对象,并读取Excel的第1个sheet(下标从0开始)
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
// 关闭流操作,在读取文件时会创建临时文件,如果不关闭,磁盘爆掉
excelReader.finish();
log.info(String.valueOf(doctorList));
return doctorList;
}
}
实现AnalysisEventListener方法
invoke方法是遍历每一条excel中数据,可以实现空数据,重复数据的检查。可以在外层定义一个List,实现数据的临时缓存。
doAfterAllAnalysed方法是在整个遍历完成后执行的方法,我在这里增加逻辑判断,如果list的大小为0,则显示该id查无此人。
@Value("${doctor.fileName}")
private String fileName;这一块引用的是yml里的属性,可以做到一定数据的解耦
2.3 Entity层
@Data
public class Doctor {
/**
* 姓名
*/
@ExcelProperty(value = "姓名")
private String name;
/**
* 企业账号
*/
@ExcelProperty(value = "企业账号")
private String id;
/**
* 身份
*/
@ExcelProperty(value = "身份")
private String identity;
/**
* 就诊量
*/
@ExcelProperty(value = "门诊就诊量")
private Integer visits;
/**
* 看诊最多日期
*/
@DateTimeFormat("yyyy年MM月dd日")
@ExcelProperty(value = "看诊最多日期")
private String maxVisitDate;
/**
* 看诊最多日看诊量
*/
@ExcelProperty(value = "看诊最多日看诊量")
private Integer maxVisit;
/**
* 住院患者治疗量
*/
@ExcelProperty(value = "住院患者治疗量")
private Integer healing;
/**
* 手术量
*/
@ExcelProperty(value = "手术量")
private Integer operation;
/**
* 手术总时长
*/
@ExcelProperty(value = "手术总时长")
private String timeOperations;
/**
* 手术最多搭档
*/
@ExcelProperty(value = "手术最多搭档")
private String partner;
/**
* 手术时长最长时间
*/
@ExcelProperty(value = "手术时长最长时间")
private String maxTimeOperations;
/**
* 手术最长结束时间
*/
@DateTimeFormat("yyyy年MM月dd日HHmm")
@ExcelProperty(value = "手术最长结束时间")
private String latestTimeOperations;
/**
* 医生生日
*/
@DateTimeFormat("mm月dd日")
@ExcelProperty(value = "医生生日")
private String birthDate;
/**
* 生日当天看诊量
*/
@ExcelProperty(value = "生日当天看诊量")
private Integer birthVisits;
/**
* 生日当天手术量
*/
@ExcelProperty(value = "生日当天手术量")
private Integer birthOperations;
/**
* 线上诊疗人次(互联网门诊)
*/
@ExcelProperty(value = "线上诊疗人次(互联网门诊)")
private Integer onlineVisits;
}
@ExcelProperty(value = " ") 注解是EasyExcel提供的,value中填写excel中对应字段名字,一定要完全对应上,不然会出现映射错误,导致读取的数据为空值。
@DateTimeFormat("yyyy年MM月dd日") 实现对日期类型数据的转换,如果只需要部分时间字段,填写对应的日期格式就好了。
完整格式: yyyy-MM-dd HH:mm:ss
2.4 excel表
3. 前端页面设计
3.1 Thyemleaf模板
Thyemleaf入门教程:http://t.csdn.cn/RDU5r
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>循环遍历获取医生信息</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<h2 class="index-f-head" style="text-align: center"> 医师节活动 <span>查询医生信息</span> </h2>
<form action="http://localhost:8080/findDoctorById" method="get">
企业账号:<input type="text" name="id" placeholder="请输入你的企业账号">
<input type="submit" value="查询">
<input type="reset" value="重置">
</form>
<br>
<input type="reset" value="清空数据" onclick="resetAll()">
<input type="button" value="查询全部" onclick="queryAll()"> <br><br><br>
<div class="index-f-body" style="text-align: center">
<table style="border-width: 1px;border-style: solid;width: 100% ">
<tr>
<td>姓名</td>
<td>企业账号</td>
<td>身份</td>
<td>门诊就诊量</td>
<td>看诊最多日期</td>
<td>看诊最多日看诊量</td>
<td>住院患者治疗量</td>
<td>手术量</td>
<td>手术总时长</td>
<td>手术最多搭档</td>
<td>手术时长最长时间</td>
<td>手术最长结束时间</td>
<td>医生生日</td>
<td>生日当天看诊量</td>
<td>生日当天手术量</td>
<td>线上诊疗人次(互联网门诊)</td>
</tr>
<div th:each="doctor : ${doctors}" style="align-items: center">
<tr>
<td th:text="${doctor.name}"></td>
<td th:text="${doctor.id}"></td>
<td th:text="${doctor.identity}"></td>
<td th:text="${doctor.visits}"></td>
<td th:text="${doctor.maxVisitDate}"></td>
<td th:text="${doctor.maxVisit}"></td>
<td th:text="${doctor.healing}"></td>
<td th:text="${doctor.operation}"></td>
<td th:text="${doctor.timeOperations}"></td>
<td th:text="${doctor.partner}"></td>
<td th:text="${doctor.maxTimeOperations}"></td>
<td th:text="${doctor.latestTimeOperations}"></td>
<td th:text="${doctor.birthDate}"></td>
<td th:text="${doctor.birthVisits}"></td>
<td th:text="${doctor.birthOperations}"></td>
<td th:text="${doctor.onlineVisits}"></td>
</tr>
</div>
</table>
</div>
</body>
<script>
function resetAll() {
window.location.href = 'http://localhost:8080/findDoctorById?id'
}
function queryAll(){
window.location.href = 'http://localhost:8080/getDoctors'
}
</script>
</html>
注意这里的html页面路径一定要放在templates文件夹下,不然会映射不到。
实际页面展示
id查询
查询全部
在按钮中添加了window.location.href的方法,实现当前页面url的跳转。这里要注意的是配置跨域,前面已经在config中配置。
Springboot启动,方法自动执行
如果想实现Springboot启动后自动实现方法读取数据,可以实现ApplicationRunner接口。
@Slf4j
@Component
@Order(value = 1)
public class MyApplicationRunner implements ApplicationRunner {
@Autowired
private IDoctorService doctorService;
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("测试MyApplicationRunner");
doctorService.readDoctors();
}
}
以上,喜欢请点个赞吧~