POI报表的入门

POI报表的入门

理解员工管理的的业务逻辑

能够说出Eureka和Feign的作用

理解报表的两种形式和POI的基本操作熟练使用POI完成Excel的导入导出操作

员工管理

需求分析

企业员工管理是人事资源管理系统中最重要的一个环节,分为对员工入职,转正,离职,调岗,员工报表导入导出 等业务逻辑。需求看似复杂,实际上都是对数据库表的基本操作。

数据库表概述

对于员工操作而言,涉及到的数据库表如下表格说明:

数据库表名称

说明

em_archive

月度员工归档表

em_positive

转正申请表

扫描二维码关注公众号,回复: 15283303 查看本文章

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条数据,我一开始以为这两种写法查出来的结果是一样的!

猜你喜欢

转载自blog.csdn.net/qq_50954361/article/details/130756476
今日推荐