让导出Excel变的easy一些!

       这两天公司让做一个报表,时间比较紧就用之前同事的代码,之前同事使用的 POI 开发的 , 我在开发的时候感觉比较繁琐,特别是对“细胞”(cell)的设置,如果一个类里面要到处两个不同的报表,那么你个组装过程将会变得异常的烦人,  即使把数据组装这部分抽取出来感觉还是很繁琐。下面就给大家推荐一种好的方法使用 XLSTransformer 来导出报表。可能我的语言不够华美不能打动让你使用XLSTransformer,下面咱们就拿某公司现有的程序做个对比:




一:使用POI 做的导出:

java类中的 exportTradeIllegalDetails 方法:

    /**

     * 导出excel

     * @param  parameter 查出来的list集合

     * @param  outputStream

     * @throws  IOException

     */

    public void exportTradeIllegalDetails(List<Map<String,Object>> parameter,ServletOutputStream outputStream) throws IOException {

       HSSFWorkbook workbook = new HSSFWorkbook();

       HSSFSheet sheet = workbook.createSheet("余额变动明细" );

        String[] headerColumns={ "操作日期" ,"用户id" ,"姓名" ,"手机号" ,"订单号" ,"解冻/冻结" ,"余额变动" ,"帐号余额" ,"项目明细" ,"项目明细内容" };

            ExcelUtil. generateHeader(workbook ,sheet,headerColumns);

            HSSFCellStyle style = ExcelUtil. getCellStyle(workbook,false);

           

           

            int rowNum = 0;

            for(Map<String,Object> r : parameter){

               rowNum++;

               Row row = sheet.createRow(rowNum);

                row.setHeightInPoints(25);

                // 操作日期

                int i=0;

                Cell cell = row.createCell(i++);

                cell.setCellStyle(style);

                cell.setCellValue(disposeStrNull(r.get( "time")));

               

                //用户id 要加密

                cell = row.createCell(i++);

                cell.setCellStyle(style);

                cell.setCellValue(disposeStrNull(r.get( "uid")));

               

                //姓名

                cell = row.createCell(i++);

                cell.setCellStyle(style);

                cell.setCellValue(disposeStrNull(r.get( "userName")));

               

                //手机号

                cell = row.createCell(i++);

                cell.setCellStyle(style);

                cell.setCellValue(disposeStrNull(r.get( "mobile")));

               

                if(r.get("orderNo" )!=null && !r.get("orderNo" ).toString().equals("")){

                   //订单号

                   cell = row.createCell(i++);

                   cell.setCellStyle(style);

                   cell.setCellValue(disposeStrNull(r.get( "orderNo")));

                } else if(r.get("contractCode" )!=null && !r.get("contractCode" ).toString().equals("")){

                   //合同号

                   cell = row.createCell(i++);

                   cell.setCellStyle(style);

                   cell.setCellValue(disposeStrNull(r.get( "contractCode")));

                } else{

                   cell = row.createCell(i++);

                   cell.setCellStyle(style);

                   cell.setCellValue( "");

                   

                }


                //解冻/冻结

                cell = row.createCell(i++);

                cell.setCellStyle(style);

                if(disposeStrNull(r.get( "type")).equals("3" )||disposeStrNull(r.get( "type")).equals("0" )){

                   cell.setCellValue(disposeStrNull(r.get( "amount")));

                } else{

                   cell.setCellValue( "0");

                }

                //余额变动

                cell = row.createCell(i++);

                cell.setCellStyle(style);

                if(disposeStrNull(r.get( "type")).equals("1" )||disposeStrNull(r.get( "type")).equals("2" )){

                   cell.setCellValue(disposeStrNull(r.get( "amount")));

                } else{

                   cell.setCellValue( "0");

                }

               

                //帐号余额

                cell = row.createCell(i++);

                cell.setCellStyle(style);

                cell.setCellValue(disposeStrNull(r.get( "totalAmount")));

               

                //项目明细

                cell = row.createCell(i++);

                cell.setCellStyle(style);

                cell.setCellValue(disposeStrNull(r.get( "proDetail")));

                //项目明细内容

                cell = row.createCell(i++);

                cell.setCellStyle(style);

                cell.setCellValue(disposeStrNull(r.get( "proConDetail")));


           

            }

            workbook.write(outputStream);

            outputStream.flush();   

            outputStream.close(); 

    }




ExcelUtil: 



import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.List;


import org.apache.poi.hssf.usermodel.HSSFCellStyle;

import org.apache.poi.hssf.usermodel.HSSFFont;

import org.apache.poi.hssf.usermodel.HSSFSheet;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import org.apache.poi.hssf.util.HSSFColor;

import org.apache.poi.ss.usermodel.Cell;

import org.apache.poi.ss.usermodel.Row;

/**

 * excel操作工具类

 *

 * @param  <T>

 */

public class ExcelUtil {

  

    public static HSSFCellStyle getCellStyle(HSSFWorkbook workbook,boolean isHeader){

        HSSFCellStyle style = workbook.createCellStyle();

        style.setBorderBottom(HSSFCellStyle.BORDER_THIN );

        style.setBorderLeft(HSSFCellStyle.BORDER_THIN );

        style.setBorderRight(HSSFCellStyle.BORDER_THIN );

        style.setBorderTop(HSSFCellStyle.BORDER_THIN );

        style.setLocked( true);

        if (isHeader) {

            style.setFillForegroundColor(HSSFColor.GREY_25_PERCENT.index);

            style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND );

            HSSFFont font = workbook.createFont();

            font.setColor(HSSFColor.BLACK.index );

            font.setFontHeightInPoints((short ) 12);

            font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD );

            style.setFont(font);

        }       

        return style;

    }

   

  

    public static void generateHeader(HSSFWorkbook workbook,HSSFSheet sheet,String[] headerColumns){

        HSSFCellStyle style = getCellStyle(workbook,true);

        Row row = sheet.createRow(0);

        row.setHeightInPoints(30);

        for(int i=0;i<headerColumns.length ;i++){

            Cell cell = row.createCell(i);

            String[] column = headerColumns[i].split("_#_" );

            if(column.length ==1){

               sheet.setColumnWidth(i, 3000);

            } else{

               sheet.setColumnWidth(i, Integer.valueOf(column[1]));

            }

           

            cell.setCellValue(column[0]);

            cell.setCellStyle(style);

        }

    }   

   

    @SuppressWarnings({ "rawtypes", "unchecked" })

    public static <T> HSSFSheet creatAuditSheet(HSSFWorkbook workbook,String sheetName,

            List<T> dataset,String[] headerColumns,String[] fieldColumns) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

       

        HSSFSheet sheet = workbook.createSheet(sheetName);

        sheet.protectSheet( "");

       

        generateHeader(workbook,sheet,headerColumns);

        HSSFCellStyle style = getCellStyle(workbook,false);

        SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd" );

        int rowNum = 0;

        for(T t:dataset){

            rowNum++ ;

            Row row = sheet.createRow(rowNum);

            row.setHeightInPoints(25);

            for(int i = 0; i < fieldColumns.length; i++){              

                String fieldName = fieldColumns[i] ;             

              

                String getMethodName = "get" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);                  

                try {                   

                    Class clazz = t.getClass();

                    Method getMethod;

                    getMethod = clazz.getMethod(getMethodName, new Class[]{} );

                    Object value = getMethod.invoke(t, new Object[]{});

                    String cellValue = "";

                    if (value instanceof Date){

                        Date date = (Date)value;

                        cellValue = sd.format(date);

                    } else{

                        cellValue = null != value ? value.toString() : "" ;

                    }                   

                    Cell cell = row.createCell(i);

                    cell.setCellStyle(style);

                    cell.setCellValue(cellValue);

                  

                } catch (Exception e) {

                   

                }

            }           

        }

        return sheet;       

    }

}


         如果现在又有一个新的报表需求并且和之前的半毛钱关系都没有的话,那么你将重复的写这三行代码

cell = row.createCell(i++); cell.setCellStyle(style);  cell.setCellValue(disposeStrNull(r.get("uid")));

可能你抽取的更多代码可能会少点,如果报表的很多的话,这些依然很繁琐!下面就让 XLSTransformer 展示展示他的威力吧




二:XLSTransformer导出


2.1  ) 在使用之前,我们先学习一下Transformer 这个类吧


javax.xml.transform

类 Transformer

java.lang.Object  [object Object]

  • public abstract class
  • extends Object

此抽象类的实例能够将源树转换为结果树。

可以通过 TransformerFactory.newTransformer 方法获取此类的实例。然后可以使用此实例处理来自不同源的 XML,并将转换输出写入各种接收器。

在多线程同时运行时不能使用此类的对象。不同线程可以同时使用不同的 Transformers。

Transformer 可以多次使用。可以在转换之间保留参数和输出属性。


构造方法摘要
protectedTransformer()
          默认构造方法受到有意保护。

 

方法摘要
abstract  voidclearParameters()
          清除所有通过 setParameter 设置的参数。
abstract  ErrorListenergetErrorListener()
          获取转换的实际错误事件处理程序。
abstract  PropertiesgetOutputProperties()
          获取转换的输出属性的副本。
abstract  StringgetOutputProperty(String name)
          获取对转换器有效的输出属性。
abstract  ObjectgetParameter(String name)
          获取通过 setParameter 显式设置的参数。
abstract  URIResolvergetURIResolver()
          获取将用于解析在 document() 中使用的 URI 的对象。
 voidreset()
          将此 Transformer 重置为其初始配置。
abstract  voidsetErrorListener(ErrorListener listener)
          设置转换的实际错误事件侦听器。
abstract  voidsetOutputProperties(Properties oformat)
          设置转换的输出属性。
abstract  voidsetOutputProperty(String name, String value)
          设置转换中实际的输出属性。
abstract  voidsetParameter(String name, Object value)
          添加一个转换参数。
abstract  voidsetURIResolver(URIResolver resolver)
          设置将用于解析在 document() 中使用的 URI 的对象。
abstract  voidtransform(Source xmlSource, Result outputTarget)
          将 XML Source 转换为 Result

 

从类 java.lang.Object 继承的方法
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

 

构造方法详细信息

Transformer

protected ()
  • 默认构造方法受到有意保护。

方法详细信息

reset

public void ()
  • 将此 Transformer 重置为其初始配置。

    Transformer 被重置为通过 TransformerFactory.newTransformer()、TransformerFactory.newTransformer(Source source) 或 Templates.newTransformer() 创建它时的状态。reset() 的设计目标是允许重用现有 Transformer,以节省与创建新 Transformer 有关的资源。

    不保证重置的 Transformer 具有相同的 URIResolver 或 ErrorListener Object,例如 Object.equals(Object obj)。但保证具有功能相同的 URIResolver 和 ErrorListener

  •  

    • 抛出:

    • UnsupportedOperationException - 当实现不能重写此方法时。

    • 从以下版本开始:

    • 1.5


transform

public abstract void (Source xmlSource,
                               Result outputTarget)
                        throws TransformerException
  • 将 XML Source 转换为 Result。当实例化 Transformer 和对 Transformer 实例进行任何修改时,指定的转换行为由 TransformerFactory 的实际设置决定。

    空 Source 表示为由 DocumentBuilder.newDocument() 构造的空文档。空 Source 的转换结果取决于转换行为;结果不总为空 Result

  •  

    • 参数:

    • xmlSource - 要转换的 XML 输入。

    • outputTarget - 转换 xmlSource 的 Result

    • 抛出:

    • TransformerException - 如果转换过程中发生不可恢复的错误。


setParameter

public abstract void (String name,
                                  Object value)
  • 添加一个转换参数。

    以两部分字符串形式传递限定名称,即用花括号括起来的名称空间 URI,后跟本地名称。如果名称中有 null URL,则 String 只包含本地名称。应用程序可以通过测试安全地检查非 null URI,以查看名称的首字符是否为 '{' 字符。

    例如,如果 URI 和本地名称是从通过 <xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/> 定义的元素获取的,则限定名称将为 "{http://xyz.foo.com/yada/baz.html}foo"。注意,不使用前缀。

  •  

    • 参数:

    • name - 参数名称,它可以以花括号({})中的名称空间 URI 开始。

    • value - 值对象。它可以为任何有效的 Java 对象。处理器负责提供正确的对象 coersion,或只传递在扩展中使用的对象。

    • 抛出:

    • NullPointerException - 如果值为 null。


getParameter

public abstract Object (String name)
  • 获取通过 setParameter 显式设置的参数。

    此方法不返回默认参数值,默认参数值直到在转换过程中计算了节点上下文后才能确定。

  •  

    • 参数:

    • name - 要获取的 Object

    • 返回:

    • 已通过 setParameter 设置的参数。


clearParameters

public abstract void ()
  • 清除所有通过 setParameter 设置的参数。


setURIResolver

public abstract void (URIResolver resolver)
  • 设置将用于解析在 document() 中使用的 URI 的对象。

    如果解析器参数为 null,则将清除 URIResolver 值,且转换器将不再拥有解析器。

  •  

    • 参数:

    • resolver - 实现 URIResolver 接口的对象,或为 null。


getURIResolver

public abstract URIResolver ()
  • 获取将用于解析在 document() 中使用的 URI 的对象。

  •  

    • 返回:

    • 实现 URIResolver 接口的对象,或返回 null。


setOutputProperties

public abstract void (Properties oformat)
  • 设置转换的输出属性。这些属性将重写通过 xsl:output 在 Templates 中设置的属性。

    如果此函数的参数为 null,则移除任何以前设置的属性,且值将恢复为 templates 对象中定义的值。

    以两部分字符串形式传递限定属性,即用花括号括起来的名称空间 URI,后跟本地名称。如果名称中有 null URL,则 String 只包含本地名称。应用程序可以通过测试安全地检查非 null URI,以查看名称的首字符是否为 '{' 字符。

    例如,如果 URI 和本地名称是从通过 <xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/> 定义的元素获取的,则限定名称将为 "{http://xyz.foo.com/yada/baz.html}foo"。注意,不使用前缀。

    如果任何参数键不能被识别且不是名称空间限定的,则抛出 IllegalArgumentException

  •  

    • 参数:

    • oformat - 将用于重写在实际转换中任何相同属性的输出属性集。

    • 抛出:

    • IllegalArgumentException - 当键无法识别且不是限定于名称空间的键时。

    • 另请参见:

    • OutputKeys, Properties


getOutputProperties

public abstract Properties ()
  • 获取转换的输出属性的副本。

    返回的属性应包含由用户设置的属性,以及通过样式表设置的属性,且这些属性将 section 16 of the XSL Transformations (XSLT) W3C Recommendation 指定的默认属性作为“默认值”。由用户或通过样式表特定设置的属性应位于基本 Properties 列表中,而未特定设置的 XSLT 默认属性应位于默认的 Properties 列表中。因此,getOutputProperties().getProperty(String key) 将包含通过 setOutputProperty(java.lang.String, java.lang.String)、setOutputProperties(java.util.Properties) 设置的任何属性,或者在样式表 默认属性中设置的任何属性,而 getOutputProperties().get(String key) 将只检索通过 setOutputProperty(java.lang.String, java.lang.String)、setOutputProperties(java.util.Properties) 显式设置的属性,或在样式表中显式设置的属性。

    注意返回的 Properties 对象的变化将不影响转换器所包含的属性。

    如果任何参数键不能被识别且不是名称空间限定的键,则属性将被忽略且不返回。换句话说,行为与 setOutputProperties 无关。

  •  

    • 返回:

    • 下一个实际转换中的输出属性集的副本。

    • 另请参见:

    • OutputKeys, Properties, XSL Transformations (XSLT) Version 1.0


setOutputProperty

public abstract void (String name,
                                       String value)
                                throws IllegalArgumentException
  • 设置转换中实际的输出属性。

    以两部分字符串形式传递限定属性名称,即用花括号括起来的名称空间 URI,后跟本地名称。如果名称中有 null URL,则 String 只包含本地名称。应用程序可以通过测试安全地检查非 null URI,以查看名称的首字符是否为 '{' 字符。

    例如,如果 URI 和本地名称是从通过 <xyz:foo xmlns:xyz="http://xyz.foo.com/yada/baz.html"/> 定义的元素获取的,则限定名称将为 "{http://xyz.foo.com/yada/baz.html}foo"。注意,不使用前缀。

    传递给 setOutputProperties(java.util.Properties) 的 Properties 对象不会受到调用此方法的影响。

  •  

    • 参数:

    • name - 指定了输出属性名称的非 null String,它可以是名称空间限定的。

    • value - 输出属性的非 null 字符串值。

    • 抛出:

    • IllegalArgumentException - 如果不支持属性,且该属性没有限定于某一名称空间。

    • 另请参见:

    • OutputKeys


getOutputProperty

public abstract String (String name)
                                  throws IllegalArgumentException
  • 获取对转换器有效的输出属性。

    如果已经使用 setOutputProperty(java.lang.String, java.lang.String) 设置了属性,则返回所设置的值。如果在样式表中显式地指定了属性,则返回所指定的值。如果使用默认属性值,即没有使用 setOutputProperty(java.lang.String, java.lang.String) 或在样式表中显式地设置了任何值,则结果将随实现以及输入样式表而改变。

  •  

    • 参数:

    • name - 指定了输出属性名称的非 null String,它可以是名称空间限定的。

    • 返回:

    • 输出属性的字符串值,如果找不到属性,则返回 null。

    • 抛出:

    • IllegalArgumentException - 如果不支持属性。

    • 另请参见:

    • OutputKeys


setErrorListener

public abstract void (ErrorListener listener)
                               throws IllegalArgumentException
  • 设置转换的实际错误事件侦听器。

  •  

    • 参数:

    • listener - 新错误侦听器。

    • 抛出:

    • IllegalArgumentException - 如果侦听器为 null。


getErrorListener

public abstract ErrorListener ()

获取转换的实际错误事件处理程序。实现必须提供默认错误侦听器。

返回:

当前错误处理程序,它永远不应为 null。


2.2 )  学习完Transformer 这个类我们就来写一个Demo试试手吧!

package com.jhaso.action;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.jxls.exception.ParsePropertyException;
import net.sf.jxls.transformer.XLSTransformer;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Workbook;

import com.jhaso.model.ContReportModel;
import com.jhaso.util.ExportImportUtil;
import com.jhaso.util.FormatUtil;

public class AgeContAction {
     public ContReportModel am = new ContReportModel();

     public String contCode;

     private static String PATH = "D:/work/jhaso/JXLTest/src/com/jhaso/template/jxls_agent_report.xls";
     private static String PATH2 = "D:/work/jhaso/JXLTest/src/com/jhaso/template/";
     
     private static List<ContReportModel> list;
     
     public AgeContAction(){
          init();
     }
     
     private void init() {
          list =  new ArrayList<ContReportModel>();
          ContReportModel crm = new ContReportModel();
          crm.setIndex(1);     
          crm.setOperTimeView("2015-08-01 00:00:00");     
          crm.setName("铁臂金刀");     
          crm.setRecharge(1000.00);     
          crm.setMargin(2000.00);     
          crm.setRebate(3560.00);     
          crm.setRefund(4560.00);     
          crm.setCosts(3560.00);     
          crm.setOperContent("铁臂金刀向郑州一百度充值");     
          list.add(crm);
          ContReportModel crm2 = new ContReportModel();
          crm2.setIndex(2);
          crm2.setOperTimeView("2015-08-01 00:00:00");     
          crm2.setName("铁臂阿童木");     
          crm2.setRecharge(1500.00);     
          crm2.setMargin(2500.00);     
          crm2.setRebate(3860.00);     
          crm2.setRefund(5590.00);     
          crm2.setCosts(4560.00);     
          crm2.setOperContent("铁臂阿童木向郑州一百度充值");
          list.add(crm2);
     }

     /**
     * 导出excel
     */
     
     public void toReportExcel(HttpServletRequest request, HttpServletResponse response) {
          // 导出数据
          String srcFilePath = PATH;
          String destFilePath = ExportImportUtil.getDataTempFile(
                    PATH2).getAbsoluteFile()
                    + File.separator + new Date().getTime() + "_report.xls";

          File detFile = new File(destFilePath);
        InputStream iputStream;
          try {
               iputStream = new FileInputStream(new File(PATH));
             Map<String,Object> beanParams = new HashMap<String,Object>();  
             beanParams.put("operat", list);  
             XLSTransformer transformer = new XLSTransformer();
               Workbook wb = (Workbook) transformer.transformXLS(iputStream, beanParams);
               /*
               * 真实项目请打开注释
               * response.setHeader(
                         "Content-Disposition",
                         "attachment;fileName="+ ExportImportUtil.generateFileName(request,FormatUtil.formatDate() + "合同报表.xls"));
             response.setContentType("application/vnd.ms-excel");*/
             OutputStream os = response.getOutputStream();
             wb.write(new FileOutputStream(destFilePath)); // 导出Excel
             os.flush();
             iputStream.close();
          } catch (FileNotFoundException e) {
               e.printStackTrace();
          } catch (ParsePropertyException e) {
               e.printStackTrace();
          } catch (InvalidFormatException e) {
               e.printStackTrace();
          } catch (IOException e) {
               e.printStackTrace();
          } 
          detFile.delete();
          System.out.println("成功");
     }
}

转载于:https://my.oschina.net/u/2505908/blog/530529

使用easy excel导出Excel文件可以通过以下步骤完成: 1. 首先,确保已经安装了Java开发环境以及easyexcel的相关依赖包,例如alibaba easyexcel。 2. 导入easyexcel相关的类和包,例如`com.alibaba.excel.EasyExcel`。 3. 创建一个ExcelWriter对象,用于写入Excel文件。可以指定要生成的Excel文件的路径和文件名。 4. 创建一个Sheet对象,并设置sheet的名称。 5. 创建一个List集合,用于存储要导出的数据。 6. 将数据逐一添加到List集合中。 7. 调用ExcelWriter对象的write方法,将数据写入到Sheet中。 8. 最后,调用ExcelWriter对象的finish方法,关闭资源,生成Excel文件。 下面是一个示例代码: ```java import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.metadata.Table; import java.util.ArrayList; import java.util.List; public class ExportExcel { public static void main(String[] args) { // 创建ExcelWriter对象,并指定要生成的Excel文件的路径及名称 ExcelWriter excelWriter = EasyExcel.write("D:\\test.xlsx").build(); // 创建Sheet对象,并设置sheet的名称 Sheet sheet = new Sheet(1, 0); sheet.setSheetName("Sheet1"); // 创建一个 List 集合,用于存储要导出的数据 List<List<String>> data = new ArrayList<>(); // 向 List 中逐一添加数据 List<String> item = new ArrayList<>(); item.add("姓名"); item.add("年龄"); data.add(item); item = new ArrayList<>(); item.add("张三"); item.add("25"); data.add(item); // 将数据写入到 Sheet 中 excelWriter.write(data, sheet); // 关闭资源,生成Excel文件 excelWriter.finish(); } } ``` 以上代码会生成一个名为test.xlsx的Excel文件,其中包含一个名为Sheet1的工作表,表格中有两列数据:姓名和年龄,其中姓名为张三,年龄为25。 这样就可以使用easyexcel导出Excel文件了。注意,使用easyexcel还可以导出更复杂的Excel文件,例如多个工作表、自定义样式等。详细的用法可以参考easyexcel的官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值