POI报表的入门2

简介: POI报表的入门

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

目录
相关文章
|
运维 监控 Oracle
应届生运维简历攻略
应届生运维简历攻略
1394 0
|
存储 缓存 关系型数据库
InnoDB的特点
InnoDB是MySQL数据库的存储引擎之一
298 0
|
9月前
|
人工智能 编解码 测试技术
Mini-InternVL:轻量级多模态大模型,4B 参数量媲美 InternVL2-76B
Mini-InternVL 是上海AI Lab联合清华等机构推出的轻量级多模态大模型,支持高效推理、跨领域适应和动态分辨率输入,适用于多种场景。
526 12
Mini-InternVL:轻量级多模态大模型,4B 参数量媲美 InternVL2-76B
|
12月前
|
测试技术 SDN 数据安全/隐私保护
使用OpenDaylight界面下发流表(上)
使用OpenDaylight界面下发流表(上)
316 0
|
Windows
LabVIEW安装第三方VISA软件后NI VISA失效
LabVIEW安装第三方VISA软件后NI VISA失效
226 0
|
Web App开发 人工智能 算法
阿里云“通义听悟”正式公测:用户可免费领100小时转写服务
用大模型自动做笔记、提取PPT、整理访谈,阿里云AI新产品“通义听悟”开放公测
10929 4
|
人工智能
POI cell.setCellType()过时的替代方案
POI cell.setCellType()过时的替代方案
Hive的空值判断函数
Hive的空值判断函数
Hive的空值判断函数
|
数据安全/隐私保护 开发者
npm install ,npm ERR code 401 Incorrect or missing password 错误原因与.npmrc 配置文件的使用
npm install ,npm ERR code 401 Incorrect or missing password 错误原因与.npmrc 配置文件的使用
npm install ,npm ERR code 401 Incorrect or missing password 错误原因与.npmrc 配置文件的使用
|
JSON Java 数据格式
json进阶---jackson底层之JsonParser理解使用
json进阶---jackson底层之JsonParser理解使用(springboot多结构参数的映射方法的实现思路)
json进阶---jackson底层之JsonParser理解使用