POI报表的入门
理解员工管理的的业务逻辑
能够说出Eureka和Feign的作用
理解报表的两种形式和POI的基本操作熟练使用POI完成Excel的导入导出操作
员工管理
需求分析
企业员工管理是人事资源管理系统中最重要的一个环节,分为对员工入职,转正,离职,调岗,员工报表导入导出 等业务逻辑。需求看似复杂,实际上都是对数据库表的基本操作。
数据库表概述
对于员工操作而言,涉及到的数据库表如下表格说明:
数据库表名称 | 说明 |
em_archive | 月度员工归档表 |
em_positive | 转正申请表 |
em_resignation | 离职申请表 |
em_transferposition | 员工调岗申请表 |
em_user_company_jobs | 员工岗位信息表 |
em_user_company_personal | 员工详细信息表 |
代码实现
服务端实现
- 创建员工微服务
ihrm_employee
- 配置文件 application.yml
- 配置Shiro核心配置类ShiroConfiguration
- 配置启动类EmployeeApplication
- 导入资源中提供的基本Controller,Service,Dao,Domain代码
前端实现
导入资源中提供的前端代码。
服务发现组件 Eureka
Eureka是Netflflix开发的服务发现框架,SpringCloud将它集成在自己的子项目spring-cloud-Netflflix中,实现SpringCloud的服务发现功能。Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮 询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒, 如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka服务端开发
- 创建ihrm_eureka模块
- 引入依赖 父工程pom.xml定义SpringCloud版本
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
ihrm_eureka模块pom.xml引入eureka-server
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
- 添加application.yml
server:
port: 6868 #服务端口
eureka:
client:
registerWithEureka: false #是否将自己注册到Eureka服务中,本身就是所有无需
注册
fetchRegistry: false #是否从Eureka中获取注册信息
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址
defaultZone: http://127.0.0.1:${server.port}/eureka/
- 配置启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
}
微服务注册
我们现在就将所有的微服务都注册到Eureka中,这样所有的微服务之间都可以互相调用了。
- 将其他微服务模块添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 修改每个微服务的application.yml,添加注册eureka服务的配置
eureka:
client:
service-url:
defaultZone: http://localhost:6868/eureka
instance:
prefer-ip-address: true
- 修改每个服务类的启动类,添加注解@EnableEurekaClient
骚戴理解:把微服务都注册到Eureka后可以通过访问:http://localhost:6868进入Eureka网站看微服务的存活情况
微服务调用组件Feign
简介
Feign是简化Java HTTP客户端开发的工具(java-to-httpclient-binder),它的灵感来自于Retrofit、JAXRS-2.0和WebSocket。Feign的初衷是降低统一绑定Denominator到HTTP API的复杂度,不区分是否为restful
快速体验
我们现在在系统微服务调用企业微服务的方法(根据ID查询部门)
- 在ihrm_system模块添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 修改ihrm_system模块的启动类,添加注解
@EnableDiscoveryClient
@EnableFeignClients
- 在Ihrm_system模块创建com.ihrm.system.client包,包下创建接口
//@FeignClient注解用于指定从哪个服务中调用功能 ,注意里面的名称与被调用的服务名保持一致
@FeignClient(value = "ihrm-company")
public interface DepartmentFeignClient {
//@RequestMapping注解用于对被调用的微服务进行地址映射
@RequestMapping(value = "/company/departments/{id}/", method = RequestMethod.GET)
public Department findById(@PathVariable("id") String id) throws Exception;
}
- 修改Ihrm_system模块的 UserController
@Autowired
private DepartmentFeignClient departmentFeignClient;
//测试通过系统微服务调用企业微服务方法
@RequestMapping(value = "/test/{id}")
public void findDeptById(@PathVariable String id){
Department dept = departmentFeignClient.findById(id);
System.out.println(dept);
}
骚戴理解:Feign组件是谁调用就写在谁那里,例如微服务A远程调用微服务B的接口,那么上面的这些步骤都写在微服务A里面
- 配置Feign拦截器添加请求头
package com.ihrm.common.feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Configuration
public class FeignConfiguration {
//配置feign拦截器,解决请求头问题
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
//获取所有浏览器发送的请求属性,请求头赋值到feign
public void apply(RequestTemplate requestTemplate) {
//请求属性
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
if(attributes != null) {
HttpServletRequest request = attributes.getRequest();
//获取浏览器发起的请求头
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement(); //请求头名称 Authorization
String value = request.getHeader(name);//请求头数据 "Bearer b1dbb4cf-7de6-41e5-99e2-0e8b7e8fe6ee"
requestTemplate.header(name,value);
}
}
}
}
};
}
}
骚戴理解:在使用Feign组件远程调用其他微服务的时候会丢失请求头,所以要写一个上面的配置类来解决这个问题,其实就是把请求头给配置好,在每次调用其他微服务的时候都把这个请求头设置进去,这样就不缺请求头了
POI报表的概述
需求说明
在企业级应用开发中,Excel报表是一种最常见的报表需求。Excel报表开发一般分为两种形式:
- 为了方便操作,基于Excel的报表批量上传数据
- 通过java代码生成Excel报表。
在Saas-HRM系统中,也有大量的报表操作,那么接下来的课程就是一起来学习企业级的报表开发。
Excel的两种形式
目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本,两者之间的区别如下:
Excel2003是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小;Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,操作效率更高
常见excel操作工具
Java中常见的用来操作Excl的方式一般有2种:JXL和POI。
JXL只能对Excel进行操作,属于比较老的框架,它只支持到Excel 95-2000的版本。现在已经停止更新和维护。POI是apache的项目,可对微软的Word,Excel,Ppt进行操作,包括office2003和2007,Excl2003和2007。poi现在一直有更新。所以现在主流使用POI。
POI的概述
Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache POI提供API
给Java语言操作Microsoft Office的功能。
POI的应用场景
- 数据报表生成
- 数据备份
- 数据批量上传
POI的入门操作
搭建环境
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
POI结构说明
- HSSF提供读写Microsoft Excel XLS格式档案的功能。
- XSSF提供读写Microsoft Excel OOXML XLSX格式档案的功能。
- HWPF提供读写Microsoft Word DOC格式档案的功能。
- HSLF提供读写Microsoft PowerPoint格式档案的功能。
- HDGF提供读Microsoft Visio格式档案的功能。
- HPBF提供读Microsoft Publisher格式档案的功能。
- HSMF提供读Microsoft Outlook格式档案的功能。
API介绍
API名称 | |
Workbook | Excel的文档对象,针对不同的Excel类型分为:HSSFWorkbook【2003版本后缀为.xls】和XSSFWorkbool【2007版本后缀为.xlsx】 |
Sheet | Excel的表单 |
Row | Excel的行 |
Cell | Excel的格子单元 |
Font | Excel字体 |
CellStyle | 格子单元样式 |
基本操作
创建Excel
public class PoiTest01 {
//测试创建excel文件
public static void main(String[] args) throws Exception {
//1.创建workbook工作簿
Workbook wb = new XSSFWorkbook();
//2.创建表单Sheet
Sheet sheet = wb.createSheet("test");
//3.文件流
FileOutputStream fos = new FileOutputStream("E:\\test.xlsx");
//4.写入文件
wb.write(fos);
fos.close();
}
}
骚戴理解:FileOutputStream fos = new FileOutputStream("E:\\test.xlsx");表示把创建的excel文件输出到E:\\test.xlsx路径下
创建单元格
//测试创建单元格
public static void main(String[] args) throws Exception {
//1.创建workbook工作簿
Workbook wb = new XSSFWorkbook();
//2.创建表单Sheet
Sheet sheet = wb.createSheet("test");
//3.创建行对象,从0开始
Row row = sheet.createRow(3);
//4.创建单元格,从0开始
Cell cell = row.createCell(0);
//5.单元格写入数据
cell.setCellValue("骚戴");
//6.文件流
FileOutputStream fos = new FileOutputStream("E:\\test.xlsx");
//7.写入文件
wb.write(fos);
fos.close();
}
骚戴理解: Row row = sheet.createRow(3);是指定第四行,Cell cell = row.createCell(0);是指定第1列,如图所示:
设置格式
package cn.itcast.poi.test;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
/**
* 单元格样式处理
*/
public class PoiTest03 {
public static void main(String[] args) throws Exception {
//创建工作簿 HSSFWorkbook -- 2003
Workbook wb = new XSSFWorkbook(); //2007版本
//创建表单sheet
Sheet sheet = wb.createSheet("test");
//创建行对象 参数:索引(从0开始)
Row row = sheet.createRow(2);
//创建单元格对象 参数:索引(从0开始)
Cell cell = row.createCell(2);
//向单元格中写入内容
cell.setCellValue("传智播客");
//样式处理
//创建样式对象
CellStyle style = wb.createCellStyle();
style.setBorderTop(BorderStyle.THIN);//上边框
style.setBorderBottom(BorderStyle.THIN);//下边框
style.setBorderLeft(BorderStyle.THIN);//左边框
style.setBorderRight(BorderStyle.THIN);//右边框
//创建字体对象
Font font = wb.createFont();
font.setFontName("华文行楷"); //字体
font.setFontHeightInPoints((short)28);//字号
style.setFont(font);
//行高和列宽
row.setHeightInPoints(50);//行高
//列宽的宽度 字符宽度
sheet.setColumnWidth(2,31 * 256);//列宽
//剧中显示
style.setAlignment(HorizontalAlignment.CENTER);//水平居中
style.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
//向单元格设置样式
cell.setCellStyle(style);
//文件流
FileOutputStream pis = new FileOutputStream("E:\\excel\\poi\\test2.xlsx");
//写入文件
wb.write(pis);
pis.close();
}
}
骚戴理解: sheet.setColumnWidth(0, 31 * 256);//设置第一列的宽度是31个字符宽度,注意这里要乘以256是因为源码里面设计的时候是会除以256
绘制图形
package cn.itcast.poi.test;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 插入图片
*/
public class PoiTest04 {
public static void main(String[] args) throws Exception {
//创建工作簿 HSSFWorkbook -- 2003
Workbook wb = new XSSFWorkbook(); //2007版本
//创建表单sheet
Sheet sheet = wb.createSheet("test");
//读取图片流
FileInputStream stream = new FileInputStream("E:\\excel\\poi\\logo.jpg");
//转化二进制数组
byte[] bytes = IOUtils.toByteArray(stream);
stream.read(bytes);
//向POI内存中添加一张图片,返回图片在图片集合中的索引
int index = wb.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);//参数一:图片的二进制数据,参数二:图片类型
//绘制图片工具类
CreationHelper helper = wb.getCreationHelper();
//创建一个绘图对象
Drawing<?> patriarch = sheet.createDrawingPatriarch();
//创建锚点,设置图片坐标
ClientAnchor anchor = helper.createClientAnchor();
anchor.setRow1(0);
anchor.setCol1(0);
//绘制图片
Picture picture = patriarch.createPicture(anchor, index);//图片位置,图片的索引
picture.resize();//自适应渲染图片
//文件流
FileOutputStream pis = new FileOutputStream("E:\\excel\\poi\\test3.xlsx");
//写入文件
wb.write(pis);
pis.close();
}
}
骚戴理解:下面的所谓的锚点其实就是图片的起始下标,例如下面设置就是在第一行第一列插入一张图片
//创建锚点,设置图片坐标
ClientAnchor anchor = helper.createClientAnchor();
anchor.setRow1(0);
anchor.setCol1(0);
加载Excel
package cn.itcast.poi.test;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.IOException;
/**
* 读取excel并解析
* sheet.getLastRowNum() : 最后一行的索引
* row.getLastCellNum() : 最后一个单元格的号码
*/
public class PoiTest05 {
public static void main(String[] args) throws Exception {
//1.根据Excel文件创建工作簿
Workbook wb = new XSSFWorkbook("E:\\excel\\poi\\demo.xlsx");
//2.获取Sheet
Sheet sheet = wb.getSheetAt(0);//参数:索引
//3.获取Sheet中的每一行,和每一个单元格
for (int rowNum = 0; rowNum<= sheet.getLastRowNum() ;rowNum ++) {
Row row = sheet.getRow(rowNum);//根据索引获取每一个行
StringBuilder sb = new StringBuilder();
for(int cellNum=2;cellNum< row.getLastCellNum(); cellNum ++) {
//根据索引获取每一个单元格
Cell cell = row.getCell(cellNum);
//获取每一个单元格的内容
Object value = getCellValue(cell);
sb.append(value).append("-");
}
System.out.println(sb.toString());
}
}
//根据单元格数据类型获取数据
public static Object getCellValue(Cell cell) {
//1.获取到单元格的属性类型
CellType cellType = cell.getCellType();
//2.根据单元格数据类型获取数据
Object value = null;
switch (cellType) {
case STRING:
value = cell.getStringCellValue();
break;
case BOOLEAN:
value = cell.getBooleanCellValue();
break;
case NUMERIC:
if(DateUtil.isCellDateFormatted(cell)) {
//日期格式
value = cell.getDateCellValue();
}else{
//数字
value = cell.getNumericCellValue();
}
break;
case FORMULA: //公式
value = cell.getCellFormula();
break;
default:
break;
}
return value;
}
}
骚戴理解:这里要注意Excel里面单元格有很多类型,所以需要判断是什么类型然后对应Java代码,其中日期和数字类型都是“NUMERIC”类型,所以需要调用DateUtil.isCellDateFormatted()去判断这个单元格的类型是不是日期类型,如果不是那就是数字类型,上面的getCellValue方法是可以提取处理作为一个工具类的
POI报表导入
需求分析
实现批量导入员工功能,页面端上传excel表格,服务端解析表格获取数据,批量新增用户
员工导入
搭建环境
父模块pom文件添加依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.0.1</version>
</dependency>
实现Excel上传
- 用户实体类配置构造方法
//objs数据位置和excel上传位置一致。
public User(Object []objs,String companyId,String companyName) {
//用户名 手机号 工号 聘用 形式 入职 时间 部门编码
this.username = values[1].toString();
this.mobile = values[2].toString();
this.createTime = new Date();
//默认手机号excel读取为字符串会存在科学记数法问题,转化处理
this.workNumber = new DecimalFormat("#").format(values[3]).toString();
this.formOfEmployment =((Double) values[4]).intValue();
this.timeOfEntry = (Date) values[5];
this.departmentId = values[6].toString(); //部门编码 != 部门id
}
骚戴理解:这里代码设计的就有问题,这个Excel里面搞了一个部门编码,然后在User里面压根就没有部门编码这个属性,所以就把部门编码的属性放到了departmentId里面,才会有部门编码 != 部门id,我一开始都被绕晕了,这样搞应该是为了用到远程调用,为了调用ihrm_company微服务来根据部门编码和公司id去查询部门id,这样设计太牵强了!
this.workNumber = new DecimalFormat("#").format(values[3]).toString(); //默认手机号excel读取为字符串会存在科学记数法问题,转化处理。
DecimalFormat("#") 在这里创建了一个 DecimalFormat 的对象,其中 # 是特殊字符,表示数字的占位符。
.format(values[3]) 表示将values[3]这个数字转换为字符串,并按照构造函数中制定的格式进行格式化。
举个例子:如果 objs[2]=1234.5678 ,那么 new DecimalFormat("#").format(objs[2]) 的返回结果就是一个字符串 "1235",因为格式中只有整数部分的占位符 #
- 在系统微服务UserController中添加上传方法
/**
* 导入Excel,添加用户
* 文件上传:springboot
*/
@RequestMapping(value="/user/import",method = RequestMethod.POST)
public Result importUser(@RequestParam(name="file") MultipartFile file) throws Exception {
//1.解析Excel
//1.1.根据Excel文件创建工作簿
Workbook wb = new XSSFWorkbook(file.getInputStream());
//1.2.获取Sheet
Sheet sheet = wb.getSheetAt(0);//参数:索引
//1.3.获取Sheet中的每一行,和每一个单元格
//2.获取用户数据列表
List<User> list = new ArrayList<>();
System.out.println(sheet.getLastRowNum());
for (int rowNum = 1; rowNum<= sheet.getLastRowNum() ;rowNum ++) {
Row row = sheet.getRow(rowNum);//根据索引获取每一个行
Object [] values = new Object[row.getLastCellNum()];
for(int cellNum=1;cellNum< row.getLastCellNum(); cellNum ++) {
Cell cell = row.getCell(cellNum);
Object value = getCellValue(cell);
values[cellNum] = value;
}
User user = new User(values);
list.add(user);
}
//3.批量保存用户
userService.saveAll(list,companyId,companyName);
return new Result(ResultCode.SUCCESS);
}
//根据单元格数据类型获取数据
public static Object getCellValue(Cell cell) {
//1.获取到单元格的属性类型
CellType cellType = cell.getCellType();
//2.根据单元格数据类型获取数据
Object value = null;
switch (cellType) {
case STRING:
value = cell.getStringCellValue();
break;
case BOOLEAN:
value = cell.getBooleanCellValue();
break;
case NUMERIC:
if(DateUtil.isCellDateFormatted(cell)) {
//日期格式
value = cell.getDateCellValue();
}else{
//数字
value = cell.getNumericCellValue();
}
break;
case FORMULA: //公式
value = cell.getCellFormula();
break;
default:
break;
}
return value;
}
调用企业微服务获取部门数据
- 在Ihrm_system模块创建com.ihrm.system.client包,包下创建接口
@FeignClient(value = "ihrm-company")
package com.ihrm.system.client;
import com.ihrm.common.entity.Result;
import com.ihrm.domain.company.Department;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 声明接口,通过feign调用其他微服务
*/
//声明调用的微服务名称
@FeignClient("ihrm-company")
public interface DepartmentFeignClient {
/**
* 调用微服务的接口
*/
@RequestMapping(value="/company/department/{id}",method = RequestMethod.GET)
public Result findById(@PathVariable(value="id") String id);
//远程调用企业微服务,根据企业编码code和企业名称获取企业信息
@RequestMapping(value="/company/department/search",method = RequestMethod.POST)
public Department findByCode(@RequestParam(value="code") String code, @RequestParam(value="companyId") String companyId);
}
- 修改UserService,注入DepartmentFeignClient
@Autowired
private DepartmentFeignClient departmentFeignClient;
保存全部用户
UserService中添加保存全部的方法
@Transactional
public void save(List<User> users) throws Exception {
for (User user : users) {
//配置密码
user.setPassword(new Md5Hash("123456",user.getMobile(),3).toString());
//配置id
user.setId(idWorker.nextId()+"");
//其他基本属性
user.setInServiceStatus(1);
user.setEnableState(1);
user.setLevel("user");
//获取部门信息
Department dept = departmentFeignClient.findById(user.getDepartmentId(),user.getCompanyId());
if(dept != null) {
user.setDepartmentId(dept.getId());
user.setDepartmentName(dept.getName());
}
userDao.save(user);
}
}
POI报表导出
需求分析
完成当月人事报表的导出:包含当月入职员工信息,离职员工信息
骚戴理解:也就是要把em_user_company_personal(员工详细信息表)和em_resignation(离职申请表)进行左外连接
人事报表导出
步骤分析
- 构造Excel表格数据创建工作簿
- 创建sheet 创建行对象
- 创建单元格对象
- 填充数据,设置样式下载
代码实现
- EmployeeReportResult
package com.ihrm.domain.employee.response;
import com.ihrm.domain.employee.EmployeeResignation;
import com.ihrm.domain.employee.UserCompanyPersonal;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.BeanUtils;
/**
* 导出报表构造的人事报表对象
*/
@Getter
@Setter
@NoArgsConstructor
@ToString
public class EmployeeReportResult {
private String userId;
private String username;
private String departmentName;
private String mobile;
private String timeOfEntry;
private String companyId;
private String sex;
/**
* 出生日期
*/
private String dateOfBirth;
/**
* 最高学历
*/
private String theHighestDegreeOfEducation;
/**
* 国家地区
*/
private String nationalArea;
/**
* 护照号
*/
private String passportNo;
/**
* 身份证号
*/
private String idNumber;
/**
* 身份证照片-正面
*/
private String idCardPhotoPositive;
/**
* 身份证照片-背面
*/
private String idCardPhotoBack;
/**
* 籍贯
*/
private String nativePlace;
/**
* 民族
*/
private String nation;
/**
* 英文名
*/
private String englishName;
/**
* 婚姻状况
*/
private String maritalStatus;
/**
* 员工照片
*/
private String staffPhoto;
/**
* 生日
*/
private String birthday;
/**
* 属相
*/
private String zodiac;
/**
* 年龄
*/
private String age;
/**
* 星座
*/
private String constellation;
/**
* 血型
*/
private String bloodType;
/**
* 户籍所在地
*/
private String domicile;
/**
* 政治面貌
*/
private String politicalOutlook;
/**
* 入党时间
*/
private String timeToJoinTheParty;
/**
* 存档机构
*/
private String archivingOrganization;
/**
* 子女状态
*/
private String stateOfChildren;
/**
* 子女有无商业保险
*/
private String doChildrenHaveCommercialInsurance;
/**
* 有无违法违纪行为
*/
private String isThereAnyViolationOfLawOrDiscipline;
/**
* 有无重大病史
*/
private String areThereAnyMajorMedicalHistories;
/**
* QQ
*/
private String qq;
/**
* 微信
*/
private String wechat;
/**
* 居住证城市
*/
private String residenceCardCity;
/**
* 居住证办理日期
*/
private String dateOfResidencePermit;
/**
* 居住证截止日期
*/
private String residencePermitDeadline;
/**
* 现居住地
*/
private String placeOfResidence;
/**
* 通讯地址
*/
private String postalAddress;
/**
* 联系手机
*/
private String contactTheMobilePhone;
/**
* 个人邮箱
*/
private String personalMailbox;
/**
* 紧急联系人
*/
private String emergencyContact;
/**
* 紧急联系电话
*/
private String emergencyContactNumber;
/**
* 社保电脑号
*/
private String socialSecurityComputerNumber;
/**
* 公积金账号
*/
private String providentFundAccount;
/**
* 银行卡号
*/
private String bankCardNumber;
/**
* 开户行
*/
private String openingBank;
/**
* 学历类型
*/
private String educationalType;
/**
* 毕业学校
*/
private String graduateSchool;
/**
* 入学时间
*/
private String enrolmentTime;
/**
* 毕业时间
*/
private String graduationTime;
/**
* 专业
*/
private String major;
/**
* 毕业证书
*/
private String graduationCertificate;
/**
* 学位证书
*/
private String certificateOfAcademicDegree;
/**
* 上家公司
*/
private String homeCompany;
/**
* 职称
*/
private String title;
/**
* 简历
*/
private String resume;
/**
* 有无竞业限制
*/
private String isThereAnyCompetitionRestriction;
/**
* 前公司离职证明
*/
private String proofOfDepartureOfFormerCompany;
/**
* 备注
*/
private String remarks;
/**
* 离职时间
*/
private String resignationTime;
/**
* 离职类型
*/
private String typeOfTurnover;
/**
* 申请离职原因
*/
private String reasonsForLeaving;
public EmployeeReportResult(UserCompanyPersonal personal, EmployeeResignation resignation) {
BeanUtils.copyProperties(personal,this);
if(resignation != null) {
BeanUtils.copyProperties(resignation,this);
}
}
}
- 配置EmployeeController
/**
* 导出人事报表
* @param month
* @throws Exception
*/
@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)
public void export(@PathVariable(name = "month") String month) throws Exception {
//1.构造数据
List<EmployeeReportResult> list = userCompanyPersonalService.findByReport(companyId,month+"%");
//2.创建工作簿
XSSFWorkbook workbook = new XSSFWorkbook();
//3.构造sheet
String[] titles = {"编号", "姓名", "手机","最高学历", "国家地区", "护照号", "籍贯", "生日", "属相","入职时间","离职类型","离职原因","离职时间"};
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
AtomicInteger headersAi = new AtomicInteger();
for (String title : titles) {
Cell cell = row.createCell(headersAi.getAndIncrement());
cell.setCellValue(title);
}
AtomicInteger datasAi = new AtomicInteger(1);
Cell cell = null;
for (EmployeeReportResult report : list) {
Row dataRow = sheet.createRow(datasAi.getAndIncrement());
//编号
cell = dataRow.createCell(0);
cell.setCellValue(report.getUserId());
//姓名
cell = dataRow.createCell(1);
cell.setCellValue(report.getUsername());
//手机
cell = dataRow.createCell(2);
cell.setCellValue(report.getMobile());
//最高学历
cell = dataRow.createCell(3);
cell.setCellValue(report.getTheHighestDegreeOfEducation());
//国家地区
cell = dataRow.createCell(4);
cell.setCellValue(report.getNationalArea());
//护照号
cell = dataRow.createCell(5);
cell.setCellValue(report.getPassportNo());
//籍贯
cell = dataRow.createCell(6);
cell.setCellValue(report.getNativePlace());
//生日
cell = dataRow.createCell(7);
cell.setCellValue(report.getBirthday());
//属相
cell = dataRow.createCell(8);
cell.setCellValue(report.getZodiac());
//入职时间
cell = dataRow.createCell(9);
cell.setCellValue(report.getTimeOfEntry());
//离职类型
cell = dataRow.createCell(10);
cell.setCellValue(report.getTypeOfTurnover());
//离职原因
cell = dataRow.createCell(11);
cell.setCellValue(report.getReasonsForLeaving());
//离职时间
cell = dataRow.createCell(12);
cell.setCellValue(report.getResignationTime());
}
String fileName = URLEncoder.encode(month+"人员信息.xlsx", "UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1")));
response.setHeader("filename", fileName);
workbook.write(response.getOutputStream());
}
骚戴理解:AtomicInteger datasAi = new AtomicInteger(1);和datasAi.getAndIncrement()这是Java中使用AtomicInteger类来实现原子操作的例子。
- AtomicInteger datasAi = new AtomicInteger(1);创建了一个对象datasAi,它是AtomicInteger类型的并初始化为1。因此,如果多个线程同时访问这个对象进行操作,则可以保证对其进行操作时是线程安全的。
- datasAi.getAndIncrement() 可以理解为"获取并增加",它的作用是先获取当前值(即值为1),然后将值自增1,返回的结果是二者相加后的值。这个操作是原子性的,也就是说,在同时有多个线程操作该对象时,只有一个线程能够成功执行它,其他线程则被阻塞等待。
export方法里的response是父控制器BaseController里的定义的
package com.ihrm.common.controller;
import com.ihrm.domain.system.response.ProfileResult;
import io.jsonwebtoken.Claims;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BaseController {
protected HttpServletRequest request;
protected HttpServletResponse response;
protected String companyId;
private String userId;
protected String companyName;
protected Claims claims;
//使用jwt方式获取
// @ModelAttribute
// public void setResAnReq(HttpServletRequest request,HttpServletResponse response) {
// this.request = request;
// this.response = response;
//
// Object obj = request.getAttribute("user_claims");
//
// if(obj != null) {
// this.claims = (Claims) obj;
// this.companyId = (String)claims.get("companyId");
// this.companyName = (String)claims.get("companyName");
// }
// }
//使用shiro获取
@ModelAttribute
public void setResAnReq(HttpServletRequest request,HttpServletResponse response) {
this.request = request;
this.response = response;
//获取session中的安全数据
Subject subject = SecurityUtils.getSubject();
//1.subject获取所有的安全数据集合
PrincipalCollection principals = subject.getPrincipals();
if(principals != null && !principals.isEmpty()){
//2.获取安全数据
ProfileResult result = (ProfileResult)principals.getPrimaryPrincipal();
this.companyId = result.getCompanyId();
this.companyName = result.getCompany();
this.userId = result.getUserId();
System.out.println(companyId);
}
}
}
- 添加service
//根据企业id和年月查询
public List<EmployeeReportResult> findByReport(String companyId, String month) {
return userCompanyPersonalDao.findByReport(companyId,month);
}
- dao层实现
@Query(value = "select new com.ihrm.domain.employee.response.EmployeeReportResult(a,b) " +
"FROM UserCompanyPersonal a LEFT JOIN EmployeeResignation b ON a.userId=b.userId WHERE a.companyId = ?1 AND a.timeOfEntry LIKE ?2 OR (b.resignationTime LIKE ?2)")
List<EmployeeReportResult> findByReport(String companyId, String month);
骚戴理解:这是一个使用JPA自定义查询语句的例子,可以通过该语句查询符合条件的员工信息。
该查询语句包含了两个数据表 UserCompanyPersonal 和 EmployeeResignation,并依据它们中的特定字段获取数据。其中:
- new com.ihrm.domain.employee.response.EmployeeReportResult() 指定查询结果返回的对象为 EmployeeReportResult 对象。
- LEFT JOIN 表示使用左连接查询,根据a.userId=b.userId实现两张表的关联查询。
- ON a.userId=b.userId 为JOIN语句中的ON条件,表示UserCompanyPersonal和EmployeeResignation表中userId字段相等时关联查询两个表的员工信息。
- WHERE 子句中的 a.companyId = ?1 条件表示筛选companyId等于第一个参数的员工信息。
- AND 连接符号指定 后续筛选条件需要满足该条件,后面跟着的 a.timeOfEntry LIKE ?2 OR (b.resignationTime LIKE ?2) 是一个复杂的逻辑运算符,经过或与运算判断timeOfEntry或resignationTime是否匹配,具体如下:
- a.timeOfEntry LIKE ?2 表示筛选timeOfEntry字段类似于第二个参数的查询值(即时间模糊匹配),满足该条件之一即可。
- OR 表示或逻辑运算符。
- (b.resignationTime LIKE ?2) 表示筛选resignationTime字段类似于第二个参数的查询值。
因此,该自定义查询语句最终返回符合条件的实体集合,并且每个EmployeeReportResult对象包含UserCompanyPersonal和EmployeeResignation表中领取的一行数据。
SELECT
*
FROM
em_user_company_personal p
LEFT JOIN em_resignation r ON p.user_id = r.user_id
WHERE
p.company_id = 1
AND p.time_of_entry LIKE '2023-01%'
OR r.resignation_time LIKE '2023-01%'
骚戴理解:这里我更喜欢转成sql语句更好理解,看着更舒服,需要注意的是2023-01%能够查出90多条数据,但是2023-1%只能查出3条数据,我一开始以为这两种写法查出来的结果是一样的!