关于Excel导出功能详解

关于Excel导出功能

该导入为正常工作中用到了,是跟一位CSDN的大神学的,照搬记录一下,顺便简练的再总结一下,方便日后开发使用。

导入依赖

不管使用什么框架,只要是使用Maven管理jar包,都需要在pom文件中引入以下依赖

<dependency>
   <groupId>net.sourceforge.jexcelapi</groupId>
   <artifactId>jxl</artifactId>
   <version>2.6.10</version>
</dependency>

前端按钮

这里用到了一个阿里巴巴的图标,这个图片代码有点长,如果有大佬会简单的使用方法,欢迎评论区交流。

<button type="button" class="layui-btn" id="download"><i class="layui-icon"><svg t="1599546601692" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1193" width="20" height="20"><path d="M819.203 405.649c0-169.66-137.541-307.19-307.201-307.19s-307.195 137.53-307.195 307.19c-113.105 0-204.8 91.69-204.8 204.801s91.695 204.801 204.8 204.801h102.4V733.33h-102.4c-67.755 0-122.88-55.12-122.88-122.88 0-67.761 55.125-122.881 122.88-122.881h81.92v-81.92c0-124.22 101.055-225.28 225.275-225.28 124.221 0 225.281 101.06 225.281 225.28v81.92h81.92c67.76 0 122.871 55.12 122.871 122.881 0 67.76-55.111 122.88-122.871 122.88h-102.4v81.921h102.4c113.09 0 204.791-91.69 204.791-204.801s-91.701-204.801-204.791-204.801z" fill="#ffffff" p-id="1194"></path><path d="M511.393 925.541l221.281-238.02-64.441-60-110.79 119.22V410.22h-92.16v336.47L354.488 627.521l-64.431 60z" fill="#ffffff" p-id="1195"></path></svg></i>员工导出</button>&nbsp;&nbsp;

前端JS

没啥说的,复制粘贴就完事了

$("#download").on('click',function () {
        var time = 500;
        layer.msg('正在拼命为您导出清单,请稍后……',{icon:16,time:time,shade:0.1});
        location.href = "/upload/downLoadExcel";
        return false;
    })

后台工具类

新建一个utils的工具包,里面新建两个工具类,CommonUtils里面有一些常用的工具类方法,ExportExcel为导出工具类
在这里插入图片描述

CommonUtils类的内容
package com.jxdinfo.hussar.utils;
import javax.imageio.ImageIO;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.math.BigDecimal;
import java.util.*;

/**
 *
 * @author eason
 *
 */
@SuppressWarnings("unchecked")
public class CommonUtils {

	public static String createUUID(){
		return UUID.randomUUID().toString().replaceAll("-", "");
	}
	public static String getMACAddress(String ip) {
		String str = "";
		String macAddress = "";
		try {
			Process p = Runtime.getRuntime().exec("nbtstat -A " + ip);
			InputStreamReader ir = new InputStreamReader(p.getInputStream());
			LineNumberReader input = new LineNumberReader(ir);
			for (int i = 1; i < 100; i++) {
				str = input.readLine();
				if (str != null) {
					if (str.indexOf("MAC Address") > 1) {
						macAddress = str.substring(
								str.indexOf("MAC Address") + 14, str.length());
						break;
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace(System.out);
		}
		return macAddress;
	}
	/**
	 * 得到文件的后缀名<br/>
	 * System.out.println(getFileSubString("yulong.jpg"));>>>.jpg
	 * @param fileName
	 * @return
	 */
	public static String getFileSubString(String fileName) {
		if (fileName != null && fileName.indexOf(".") != -1) {
			return fileName.substring(fileName.indexOf("."));
		}
		return "";
	}

	/**
	 * 判断字符串是否包含内容
	 *
	 */
	public static boolean isNotEmpty(String str) {
		return str != null && !str.trim().equals("");
	}

	/**
	 * 判断数字是否大于零
	 *
	 */
	public static boolean isGtZero(Long ls) {
		return ls != null && ls > 0;
	}

	/**
	 * 判断数字是否大于零
	 *
	 */
	public static boolean isGtZero(Integer ls) {
		return ls != null && ls > 0;
	}

	/**
	 * 随机count位数字字符串<br/>
	 * 可以用来生成代金券号码和密码
	 *
	 * @param count
	 *            随机多少位
	 */
	public static String getRandomString(int count) {
		if (count > 0) {
			StringBuilder buf = new StringBuilder();
			Random rd = new Random();
			for (int i = 0; i < count; i++) {
				buf.append(rd.nextInt(10));
			}
			return buf.toString();
		} else {
			return "";
		}

	}

	/**
	 * 隐藏字符串中部分敏感信息
	 *
	 * @param tg
	 *            目标字符串
	 * @param start
	 *            开始索引
	 * @param end
	 *            结束索引
	 * @return
	 */
	public static String hidepartChar(String tg, int start, int end) {
		return new StringBuffer(tg).replace(start, end, "**").toString();
	}

	/**
	 * 判断字符在字符串中出现的次数
	 *
	 * @param tg
	 * @param fg
	 * @return
	 */
	public static Integer getCountInStr(String tg, char fg) {
		if (isNotEmpty(tg)) {
			int i = 0;
			if (isNotEmpty(tg)) {
				char ch[] = tg.toCharArray();
				for (char c : ch) {
					if (c == fg) {
						i++;
					}
				}
			}
			return i;
		}
		return 0;
	}

	/***
	 * 对象数组转换成对象集合
	 *
	 * @param <T>
	 * @param ts
	 * @return
	 */
	public static <T> List<T> getList(T... ts) {
		return Arrays.asList(ts);
	}

	/**
	 * 得到long类型集合 如果遇到不能转换为long类型的字符串跳过 返回能转换为long类型的long类型集合
	 *
	 * @param strings
	 * @return
	 */
	public static List<Long> getListByStrs(String... strings) {
		List<Long> list = new ArrayList<Long>();
		if (strings != null) {
			for (String s : strings) {
				try {
					if (isNotEmpty(s)) {
						list.add(Long.valueOf(s));
					}
				} catch (NumberFormatException e) {
				}
			}
		}
		return list;
	}

	/**
	 * 把首字母变成大写
	 *
	 * @return
	 */
	public static String toUpcaseFist(String str) {
		if (str != null && !str.trim().equals("")) {
			if (str.length() == 1) {
				return str.toUpperCase();
			} else {
				return str.substring(0, 1).toUpperCase() + str.substring(1);
			}
		} else {
			return "";
		}

	}

	/**
	 * 根据键值对得到map<String,Object>对象
	 *
	 * @param ag
	 * @return
	 */
	public static LinkedHashMap<String, Object> getMap(Object... ag) {
		LinkedHashMap<String, Object> mp = new LinkedHashMap<String, Object>();
		if (ag != null && ag.length > 0 && ag.length % 2 == 0) {
			int i = 0;
			for (@SuppressWarnings("unused") Object o : ag) {
				mp.put(String.valueOf(ag[i]), ag[++i]);
				i++;
				if (i == ag.length) {
					break;
				}

			}
		}
		return mp;
	}

	/**
	 * 根据键值对得到map<String,String>对象
	 *
	 * @param ag
	 * @return
	 */
	public static LinkedHashMap<String, String> getStrValueMap(String... ag) {
		LinkedHashMap<String, String> mp = new LinkedHashMap<String, String>();
		if (ag != null && ag.length > 0 && ag.length % 2 == 0) {
			int i = 0;
			for (@SuppressWarnings("unused") String o : ag) {
				mp.put(ag[i], ag[++i]);
				i++;
				if (i == ag.length) {
					break;
				}

			}
		}
		return mp;
	}

	/**
	 * 动态图像转换成静态图片
	 *
	 */
	public static void convertImageToStatic(File file) {
		if (file != null) {
			try {
				BufferedImage bufferedimage = ImageIO.read(file);
				if (bufferedimage != null) {
					ImageIO.write(bufferedimage, "gif", file);// 1131.gif是静态的
				}
			} catch (IOException e) {
			}
		}
	}

	/**
	 * 判断是否为数字
	 *
	 * @param tg
	 * @return
	 */
	public static Boolean isNumber(String tg) {
		if (isNotEmpty(tg)) {
			try {
				Double.valueOf(tg);
				return true;
			} catch (NumberFormatException e) {
			}
		}
		return false;
	}

	/**
	 * 处理浮点数相加运算
	 *
	 * @param v
	 * @param v2
	 * @return
	 */
	public static Double floatAdd(Double v, Double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.add(b2).doubleValue();
	}

	/**
	 * 处理浮点数相减运算
	 *
	 * @param v
	 *            被减数
	 * @param v2
	 *            减数
	 * @return
	 */
	public static Double floatSubtract(Double v, Double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.subtract(b2).doubleValue();
	}

	/**
	 * 处理浮点数相除
	 *
	 * @param v
	 * @param v2
	 * @return
	 */
	public static Double floatDiv(Double v, Double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.divide(b2).doubleValue();
	}

	/**
	 * 处理浮点数相乘
	 *
	 * @param v
	 * @param v2
	 * @return
	 */
	public static Double floatMulply(Double v, Double v2) {
		BigDecimal b1 = new BigDecimal(Double.toString(v));
		BigDecimal b2 = new BigDecimal(Double.toString(v2));
		return b1.multiply(b2).doubleValue();
	}

	/**
	 * 判断集合是否不为空
	 *
	 * @param list
	 * @return
	 */
	public static Boolean isNotEmpty(@SuppressWarnings("rawtypes") Collection list) {
		return list != null && list.size() > 0;
	}

	/**
	 * 判断数组是否不为空
	 *
	 * @param arr
	 * @return
	 */
	public static Boolean isNotEmptyArr(Object[] arr) {
		return arr != null && arr.length > 0;
	}



	/**
	 * jndi配置信息
	 */
	private static ResourceBundle JNDI;
	/**
	 * JNDI上下文初始化
	 */
	public static InitialContext init;

	// 初始化环境变量
	private static void initJNDI() {
		try {
			JNDI = ResourceBundle.getBundle("jndi");
			Properties prop = new Properties();
			prop.put(Context.INITIAL_CONTEXT_FACTORY, JNDI
					.getObject(Context.INITIAL_CONTEXT_FACTORY));
			prop
					.put(Context.PROVIDER_URL, JNDI
							.getObject(Context.PROVIDER_URL));
			init = new InitialContext(prop);
		} catch (NamingException e) {
			e.printStackTrace();
		}

	}


	/**
	 * 对页面显示内容进行编码
	 *
	 * @param str
	 * @return
	 */
	public static String htmlEncoding(String str) {
		StringBuffer bfu = new StringBuffer();
		if (str != null) {
			String s = "&#";
			char[] cs = str.toCharArray();
			for (char c : cs) {
				int it = c;
				bfu.append(s).append(it).append(";");
			}
		}
		return bfu.toString();

	}

	/**
	 * 得到json数据格式
	 *
	 * @param flag
	 *            obj[key] key数组
	 * @param property
	 *            name,age..对象属性数组
	 * @param values
	 *            yulong,22.对象属性对应的值
	 * @return
	 */
	public static StringBuffer getJson(String[] flag, String[] property,
									   List<String[]> values) {
		StringBuffer buf = new StringBuffer();
		if (flag != null && property != null && property.length > 0) {
			if (values != null && values.size() > 0
					&& property.length == values.get(0).length
					&& values.size() == flag.length) {
				Iterator<String[]> ite = values.iterator();
				buf.append("({");
				for (int j = 0; j < flag.length; j++) {
					buf.append("\"").append(flag[j]).append("\"").append(":");
					String[] ss = ite.next();
					buf.append("{");
					for (int i = 0; i < property.length; i++) {
						buf.append(property[i]).append(":").append("\"")
								.append(ss[i]).append("\"");
						if (property.length - 1 > i) {
							buf.append(",");
						}
					}
					buf.append("}");
					if (ite.hasNext()) {
						buf.append(",");
					}
				}
				buf.append("})");
			}

		}
		return buf;
	}

}
ExportExcel里的内容
package com.jxdinfo.hussar.utils;

import jxl.SheetSettings;
import jxl.write.*;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.util.*;

public class ExportExcel {
    public static int exportToExcel(HttpServletResponse response,
                                    List<Map<String, Object>> objData, String sheetName,
                                    List<String> columns) {
        int flag = 0;
        //声明工作簿jxl.write.WritableWorkbook
        WritableWorkbook wwb;
        try {
            //根据传进来的file对象创建可写入的Excel工作薄
            OutputStream os = response.getOutputStream();

            wwb = jxl.Workbook.createWorkbook(os);

            /*
             * 创建一个工作表、sheetName为工作表的名称、"0"为第一个工作表
             * 打开Excel的时候会看到左下角默认有3个sheet、"sheet1、sheet2、sheet3"这样
             * 代码中的"0"就是sheet1、其它的一一对应。
             * createSheet(sheetName, 0)一个是工作表的名称,另一个是工作表在工作薄中的位置
             */
            WritableSheet ws = wwb.createSheet(sheetName, 0);

            SheetSettings ss = ws.getSettings();
            ss.setVerticalFreeze(1);//冻结表头

            WritableFont font1 = new WritableFont(WritableFont.createFont("微软雅黑"), 10, WritableFont.BOLD);
            // WritableFont font2 =new WritableFont(WritableFont.createFont("微软雅黑"), 9 ,WritableFont.NO_BOLD);
            WritableCellFormat wcf = new WritableCellFormat(font1);
            // WritableCellFormat wcf2 = new WritableCellFormat(font2);
            // WritableCellFormat wcf3 = new WritableCellFormat(font2);//设置样式,字体

            //创建单元格样式
            //WritableCellFormat wcf = new WritableCellFormat();

            //背景颜色
            // wcf.setBackground(jxl.format.Colour.YELLOW);
//            wcf.setAlignment(Alignment.CENTRE);  //平行居中
//            wcf.setVerticalAlignment(VerticalAlignment.CENTRE);  //垂直居中
            //  wcf3.setAlignment(Alignment.CENTRE);  //平行居中
            //  wcf3.setVerticalAlignment(VerticalAlignment.CENTRE);  //垂直居中
            //  wcf3.setBackground(Colour.LIGHT_ORANGE);
            // wcf2.setAlignment(Alignment.CENTRE);  //平行居中
            // wcf2.setVerticalAlignment(VerticalAlignment.CENTRE);  //垂直居中

            /*
             * 这个是单元格内容居中显示
             * 还有很多很多样式
             */
            //   wcf.setAlignment(Alignment.CENTRE);
            //判断一下表头数组是否有数据
            if (columns != null && columns.size() > 0) {

                //循环写入表头
                for (int i = 0; i < columns.size(); i++) {
                    ws.setColumnView(i, 28);//设置列宽
//                    ws.setRowView(i+1, 600, false); //设置行高
                    /*
                     * 添加单元格(Cell)内容addCell()
                     * 添加Label对象Label()
                     * 数据的类型有很多种、在这里你需要什么类型就导入什么类型
                     * 如:jxl.write.DateTime 、jxl.write.Number、jxl.write.Label
                     * Label(i, 0, columns[i], wcf)
                     * 其中i为列、0为行、columns[i]为数据、wcf为样式
                     * 合起来就是说将columns[i]添加到第一行(行、列下标都是从0开始)第i列、样式为什么"色"内容居中
                     */
                    ws.addCell(new Label(i, 0, columns.get(i), wcf));
                }

                //判断表中是否有数据
                if (objData != null && objData.size() > 0) {
                    //循环写入表中数据
                    for (int i = 0; i < objData.size(); i++) {

                        //转换成map集合{activyName:测试功能,count:2}
                        Map<String, Object> map = objData.get(i);
                        //循环输出map中的子集:既列值
                        int j = 0;
                        DecimalFormat decimalFormat = new DecimalFormat("0.00");
                        ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
                        for (Object o : map.keySet()) {
                            //ps:因为要“”通用”“导出功能,所以这里循环的时候不是get("Name"),而是通过map.get(o)
                            String content = "";
                            if (map.get(o).toString().contains(".") && CommonUtils.isNumber(map.get(o).toString())) {
                                content = decimalFormat.format(Float.valueOf(map.get(o).toString()));
                                ws.addCell(new Label(j, i + 1, content));
                            } else if (map.get(o).toString().contains("-") && map.get(o).toString().contains(":")) {
                                content = String.valueOf(map.get(o)).split("\\.")[0];
                                ws.addCell(new Label(j, i + 1, content));
                            }
                            //图片处理
//                            else if (map.get(o).toString().contains("http") || map.get(o).toString().contains("https")){
//                                ws.setColumnView(j, 15);//设置列宽
//                                String path ="/resources/"+ String.valueOf(map.get(o)).split("upload/")[1];
//                                File imgFile = new File(path);
//                                WritableImage image = new WritableImage(j,i+1,1,1,imgFile);
//                                ws.addImage(image);
//                            }
                            else {
                                content = String.valueOf(map.get(o));
                                ws.addCell(new Label(j, i + 1, content));
                            }
                            j++;
                        }

                       /* for(int b=0;b<map.size();b++){
                        	 ws.addCell(new Label(b,i+1,String.valueOf(map.get(String.valueOf(b)))));
                        }*/
                    }
                } else {
                    flag = -1;
                }

                //写入Exel工作表
                wwb.write();

                //关闭Excel工作薄对象
                wwb.close();

                //关闭流
                os.flush();
                os.close();
            }
        } catch (IllegalStateException e) {
            System.err.println(e.getMessage());
        } catch (Exception ex) {
            flag = 0;
            ex.printStackTrace();
        }

        return flag;
    }

    /**
     * 将读取到的文件重新写入新的文件
     *
     * @param request
     */
    public static void createExcel(List<Map<String, Object>> list, HttpServletResponse response, HttpServletRequest request, String entname) throws IOException {
        OutputStream output = response.getOutputStream();
        org.apache.poi.ss.usermodel.Workbook tempWorkBook = null;
        Sheet tempSheet = null;
        int rowIndex = 1;
        Row tempRow = null;
        Cell tempCell = null;
        InputStream inputstream = null;
        try {
            inputstream = request.getSession().getServletContext().getResourceAsStream("/resources/模板.xlsx");
            // 获取模板
            tempWorkBook = new XSSFWorkbook(inputstream);
            // 获取模板sheet页
            tempSheet = tempWorkBook.getSheetAt(0);
            //全部行
            int rows = tempSheet.getPhysicalNumberOfRows();
            response.setHeader("Content-Disposition", "attachment;filename=" + new String((entname + "企业信息").getBytes("gb2312"), "ISO8859-1") + ".xlsx");
            // 将数据写入excel
            for (int i = 0; i < rows - 1; i++) {

                // 获取指定行
                tempRow = tempSheet.getRow(rowIndex++);
                //所有的单元格
                int cells = tempRow.getPhysicalNumberOfCells();
                // 获取指定行的单元格
                for (int j = 0; j < cells; j++) {
                    tempCell = tempRow.getCell(j);
                    for (int x = 0; x < list.size(); x++) {
                        Set<String> set = list.get(x).keySet();
                        Iterator iterator = set.iterator();
                        while (iterator.hasNext()) {
                            String key = (String) iterator.next();
                            String value = (String) list.get(x).get(key);
//                            System.out.println("===="+key+"====="+value);
                            if (tempCell.getStringCellValue().equals(key)) {
                                // 给单元格设值
                                tempRow.getCell(j + 1).setCellValue(value);
                            }
                        }
                    }
                }

//                if (i == (list.size() - 1)) {
//
//                    // 创建一行 统计
//                    tempRow = tempSheet.getRow(rowIndex);
//                    int cellNo2 = 2;
//                    // 获取单元格
//                    tempCell = tempRow.getCell(cellNo2++);
//                    // 给单元格设值
//                    tempCell.setCellValue(new BigDecimal(plusAmount).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
//                    tempCell = tempRow.getCell(cellNo2++);
//                    tempCell.setCellValue(new BigDecimal(reduceAmount).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
//                    tempCell = tempRow.getCell(cellNo2++);
//                    tempCell.setCellValue(new BigDecimal(afterAmount).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
//                    cellNo2++;
//                    tempCell = tempRow.getCell(cellNo2++);
//                    tempCell.setCellValue(new BigDecimal(preAmount).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
//                }

            }
            // 将内容写入Excel
            tempWorkBook.write(output);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }


    /**
     * 为指定的sheet页赋值
     *
     * @param list
     * @param tempSheet
     * @param rowIndex
     * @param rows
     * @param row
     */
    private static void setSheetDetail(List<Map<String, Object>> list, Sheet tempSheet, int rowIndex, int rows, Row row) {
        Row tempRow;
        Cell tempCell;
        for (int i = 0; i < list.size(); i++) {
            Set<String> set = list.get(i).keySet();
            Iterator iterator = set.iterator();
            // 创建指定行
            tempRow = tempSheet.createRow(rowIndex++);
            while (iterator.hasNext()) {
                String key = String.valueOf(iterator.next());
                String value = String.valueOf(list.get(i).get(key));
                for (int j = 0; j < rows; j++) {
                    // 获取指定行的单元格
                    tempCell = row.getCell(j);
                    if (key.equals(tempCell.getStringCellValue())) {
                        tempRow.createCell(j).setCellValue(value);
                    }
                }
            }
        }
    }

}

后台Controller方法

@RequestMapping("/downLoadExcel")
    @ResponseBody
    public void exportStatisticsReport(HttpServletRequest request, HttpServletResponse response){
        //使用LinkedHashMap,因为这个是有序的map
        LinkedHashMap<String,Object> reportData = new LinkedHashMap<>();
        //装载数据,就是要导出到excel的数据
        reportData.put("test1","流水号");
        reportData.put("test2","辖区");
        reportData.put("test3","业务状态");
        reportData.put("test4","企业名称");
        reportData.put("test5","网点");

        //把要导出到excel的数据的LinkedHashMap装载到这个List里面,这是导出工具类要求封装格式.
        List<Map<String, Object>> exportData = new ArrayList<>();
        exportData.add(reportData);

        //表格列名用ArrayList装载
        List<String> columns = new ArrayList<>();
        //设置excel表格中的列名
        columns.add("流水号");
        columns.add("辖区");
        columns.add("业务状态");
        columns.add("企业名称");
        columns.add("网点");

        //点击导出按钮的时候,页面上显示的标题,同时也是sheet的名称
        String filename ="测试导出excel";
        try {
            //处理一下中文乱码问题
            response.setHeader("Content-Disposition", "attachment;filename="+new String(filename.getBytes("gb2312"), "ISO8859-1")+".xls");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //以上均为数据准备,下面开始调用导出excel工具类
        ExportExcel.exportToExcel(response, exportData, filename, columns);
    }

OK,最后感谢以下作者提供的帮助,本文章本作者不盈利,仅仅记录日常遇到的一些功能和bug,点赞或者打赏请到
https://blog.csdn.net/BelleHarvester/article/details/88710519
这是原作者链接

猜你喜欢

转载自blog.csdn.net/qq_45593609/article/details/108467849