aspose实现word,excel等文件预览

简介: aspose实现word,excel等文件预览

package com.ruoyi.pams.util;

import com.aspose.cells.PdfSaveOptions;
import com.aspose.cells.Workbook;
import com.aspose.words.Document;
import com.aspose.words.License;
import com.aspose.words.SaveFormat;
import org.apache.commons.io.FileUtils;

import java.io.*;

public class Word2PdfAsposeUtil {

public static boolean getLicense() {
    boolean result = false;
    try {
        // license.xml应放在..\WebRoot\WEB-INF\classes路径下
        //InputStream is = Word2PdfAsposeUtil.class.getClassLoader().getResourceAsStream("\\license.xml");

        InputStream is = Word2PdfAsposeUtil.class.getClassLoader().getResourceAsStream("license.xml");
        License aposeLic = new License();
        aposeLic.setLicense(is);
        result = true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return result;
}

public static boolean doc2pdf(String inPath, String outPath) {
    // 验证License 若不验证则转化出的pdf文档会有水印产生
    if (!getLicense()) {
        return false;
    }
    FileOutputStream os = null;
    try {
        long old = System.currentTimeMillis();
        File file = new File(outPath); // 新建一个空白pdf文档
        os = new FileOutputStream(file);

        //Address是将要被转化的word文档,全面支持DOC, DOCX, OOXML, RTF HTML,OpenDocument, PDF,EPUB, XPS, SWF 相互转换
        Document doc = new Document(inPath);
        doc.save(os, SaveFormat.PDF);

        long now = System.currentTimeMillis();

        //转化用时
        System.out.println("word->pdf转换成功,共耗时:" + ((now - old) / 1000.0) + "秒");
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    } finally {
        if (os != null) {
            try {
                os.flush();
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return true;
}


/**
 * excel 转为pdf 输出。
 *
 * @param sourceFilePath excel文件
 * @param desFilePathd   pad 输出文件目录
 */
public static void excel2pdf(String sourceFilePath, String desFilePathd) {
    // 验证License 若不验证则转化出的pdf文档会有水印产生
    if (!getLicense()) {
        return;
    }
    try {
        Workbook wb = new Workbook(sourceFilePath);// 原始excel路径
        FileOutputStream fileOS = new FileOutputStream(desFilePathd);
        PdfSaveOptions pdfSaveOptions = new PdfSaveOptions();
        pdfSaveOptions.setOnePagePerSheet(true);//把内容放在一张PDF 页面上;
        wb.save(fileOS, pdfSaveOptions);
        fileOS.flush();
        fileOS.close();

    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 将word txt转换成pdf
 * @param inPath
 * @param outPath
 * @author zsqing
 */
public void wordAndTextToPdf(String inPath, String outPath /*, String localIP, HttpServletRequest request*/)
{
    String fileToPdfUrl="";
    boolean flag = false;
    File file = null;
    FileOutputStream os = null;
    try
    {
        //long old = System.currentTimeMillis();
        // 新建一个空白文档
        file = new File(outPath);
        file = saveAsUTF8(file);
        os = new FileOutputStream(file);
        // InPath是将要被转化的文档
        com.aspose.words.Document doc = new com.aspose.words.Document(inPath);
        /*
         * 全面支持DOC,DOCX进行OOXML,RTF,HTML,OpenDocument,PDF,EPUB,XPS,SWF间转换
         */
        doc.save(os, SaveFormat.PDF);
        flag = true;
        //long now = System.currentTimeMillis();
        //System.out.println("共耗时:" + ((now - old) / 1000.0) + "秒"); // 转化用时

    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
    finally
    {
        try
        {
            if (os != null)
            {
                os.close();
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        if (!flag)
        {
            file.deleteOnExit();
        }
    }
}

/* 将txt 转换编码
 * @param file
 * @author zsqing
 */
public File saveAsUTF8(File file){
    String code = "gbk";
    byte[] head = new byte[3];
    try {
        InputStream inputStream = new FileInputStream(file);
        inputStream.read(head);
        if (head[0] == -1 && head[1] == -2) {
            code = "UTF-16";
        } else if (head[0] == -2 && head[1] == -1) {
            code = "Unicode";
        } else if (head[0] == -17 && head[1] == -69 && head[2] == -65) {
            code = "UTF-8";
        }
        inputStream.close();

        System.out.println(code);
        if (code.equals("UTF-8")) {
            return file;
        }
        String str = FileUtils.readFileToString(file, code);
        FileUtils.writeStringToFile(file, str, "UTF-8");
        System.out.println("转码结束");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return file;
}

package com.ruoyi.pams.service.impl;

import java.io.*;
import java.util.Arrays;
import java.util.List;

import com.ruoyi.common.annotation.DataAuthorityScope;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.config.UploadConfig;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.user.CustomException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.ApplicationContextUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.pams.domain.PtpFileupload;
import com.ruoyi.pams.domain.TeFileupload;
import com.ruoyi.pams.mapper.PtpFileuploadMapper;
import com.ruoyi.pams.mapper.TeFileuploadMapper;
import com.ruoyi.pams.util.Word2PdfAsposeUtil;
import com.ruoyi.system.domain.SysConfig;
import com.ruoyi.system.mapper.SysConfigMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.weaver.loadtime.Aj;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import com.ruoyi.pams.mapper.LaboratoryFileuploadMapper;
import com.ruoyi.pams.domain.LaboratoryFileupload;
import com.ruoyi.pams.service.ILaboratoryFileuploadService;
import org.springframework.web.multipart.MultipartFile;
import javaslang.control.Try;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

/**

  • 实验室认可和资质认定Service业务层处理
    *
  • @author wangwei
  • @date 2023-05-05
    */
    @Service
    @Slf4j

public class LaboratoryFileuploadServiceImpl implements ILaboratoryFileuploadService {
@Autowired
private LaboratoryFileuploadMapper laboratoryFileuploadMapper;

@Autowired
private PtpFileuploadMapper ptpFileuploadMapper;

@Autowired
private TeFileuploadMapper teFileuploadMapper;

@Autowired
private SysConfigMapper sysConfigMapper;

/**
 * 允许上传的格式
 */
private final List<String> ALLOW_EXCEL_FORM
        = Arrays.asList(".doc", ".docx", ".xls", ".xlsx", ".ppt", ".txt", ".pdf", ".png", ".jpg", ".sql");


/**
 * 查询实验室认可和资质认定
 *
 * @param lfid 实验室认可和资质认定主键
 * @return 实验室认可和资质认定
 */
@Override
public LaboratoryFileupload selectLaboratoryFileuploadByLfid(Long lfid) {
    return laboratoryFileuploadMapper.selectLaboratoryFileuploadByLfid(lfid);
}

/**
 * 查询实验室认可和资质认定列表
 *
 * @param laboratoryFileupload 实验室认可和资质认定
 * @return 实验室认可和资质认定
 */
@Override
public List<LaboratoryFileupload> selectLaboratoryFileuploadList(LaboratoryFileupload laboratoryFileupload) {
    return laboratoryFileuploadMapper.selectLaboratoryFileuploadList(laboratoryFileupload);
}

/**
 * 新增实验室认可和资质认定
 *
 * @param laboratoryFileupload 实验室认可和资质认定
 * @return 结果
 */
@Override
@Log(title = "实验室认可和资质认定", businessType = BusinessType.INSERT)

//@DataAuthorityScope
public AjaxResult insertLaboratoryFileupload(LaboratoryFileupload laboratoryFileupload) {
    int i = laboratoryFileuploadMapper.insertLaboratoryFileupload(laboratoryFileupload);
    if (i == 0) {
        return AjaxResult.error("新增业务单据数据失败,请联系管理员!");
    }

    Integer integer = laboratoryFileuploadMapper.selectMaxId("select @@IDENTITY;");
    LaboratoryFileupload laboratoryFileupload1 = laboratoryFileuploadMapper.selectLaboratoryFileuploadByLfid(Long.valueOf(integer));
    return AjaxResult.success(laboratoryFileupload1);
}

/**
 * 修改实验室认可和资质认定
 *
 * @param laboratoryFileupload 实验室认可和资质认定
 * @return 结果
 */
@Log(title = "实验室认可和资质认定", businessType = BusinessType.UPDATE)

@Override
//@DataAuthorityScope
public int updateLaboratoryFileupload(LaboratoryFileupload laboratoryFileupload) {
    return laboratoryFileuploadMapper.updateLaboratoryFileupload(laboratoryFileupload);
}

/**
 * 批量删除实验室认可和资质认定
 *
 * @param lfids 需要删除的实验室认可和资质认定主键
 * @return 结果
 */
@Override
public int deleteLaboratoryFileuploadByLfids(Long[] lfids) {
    return laboratoryFileuploadMapper.deleteLaboratoryFileuploadByLfids(lfids);
}

/**
 * 删除实验室认可和资质认定信息
 *
 * @param lfid 实验室认可和资质认定主键
 * @return 结果
 */
@Override
public int deleteLaboratoryFileuploadByLfid(Long lfid) {
    return laboratoryFileuploadMapper.deleteLaboratoryFileuploadByLfid(lfid);
}

/**
 * 附件上传
 *
 * @param file
 * @param id
 * @return
 */
@Override
public String uploadPreviewFile(MultipartFile file, int id, int funNo) {
    // 判断附件类型
    String extension = "." + FileUploadUtils.getExtension(file);
    if (!ALLOW_EXCEL_FORM.contains(extension.toLowerCase())) {
        throw new CustomException("请上传正确的文件类型");
    }
    /*Date date = null;
    DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    String now = df.format(date);
    String[] yearMonthDay = now.split("-");*/
    UploadConfig uploadConfig = ApplicationContextUtils.getBean(UploadConfig.class);
    String shortPath = uploadConfig.getRootUploadPath()  /*+ yearMonthDay[0] + File.separator + yearMonthDay[1] + File.separator*/;
    String[] split = file.getOriginalFilename().split("\\.");
    String primaryKey = split[0];        // 删除老的
    // TMisBlobTemplate templateToDelete = tMisBlobTemplateMapper.selectTMisBlobTemplateByFunNo(funNo);
    LaboratoryFileupload laboratoryFileupload = laboratoryFileuploadMapper.selectLaboratoryFileuploadByLfid(Long.valueOf(id));

/ if (laboratoryFileupload != null) {
FileUtils.deleteFile(
shortPath + laboratoryFileupload.getLfid()+ laboratoryFileupload.getType());
laboratoryFileuploadMapper.deleteLaboratoryFileuploadByLfid(Long.valueOf(id));
}
/

    String filePath = primaryKey + extension;


    // 临时文件
    File localTempFile = new File(shortPath + primaryKey + extension);
    if (!localTempFile.getParentFile().exists()) {
        localTempFile.getParentFile().mkdir();
    }
    if (!localTempFile.exists()) {
        Try.run(localTempFile::createNewFile).onFailure(e -> log.error("新增文件失败"));
    }
    Try.run(() -> file.transferTo(localTempFile)).onFailure(e -> log.error("上传失败"));

    if (1020 == funNo) {//实验室认可和资质
        LaboratoryFileupload newLaboratoryFileupload = new LaboratoryFileupload();
        newLaboratoryFileupload.setLfid(Long.valueOf(id));
        newLaboratoryFileupload.setType(extension);
        newLaboratoryFileupload.setFilename(primaryKey);
        newLaboratoryFileupload.setFilepath(filePath);
        int i = laboratoryFileuploadMapper.updateLaboratoryFileupload(newLaboratoryFileupload);
        if (i != 1) {
            throw new CustomException("文件上传失败!");
        }
    } else if (1030 == funNo) {//  能力提供者
        PtpFileupload ptpFileupload = new PtpFileupload();
        ptpFileupload.setLfid(Long.valueOf(id));
        ptpFileupload.setType(extension);
        ptpFileupload.setFilename(primaryKey);
        ptpFileupload.setFilepath(filePath);
        int i = ptpFileuploadMapper.updatePtpFileupload(ptpFileupload);
        if (i != 1) {
            throw new CustomException("文件上传失败!");
        }
    } else if (1040 == funNo) {//  常用文件
        TeFileupload teFileupload = new TeFileupload();
        teFileupload.setFileuploadid(Long.valueOf(id));
        teFileupload.setType(extension);
        teFileupload.setFilename(primaryKey);
        teFileupload.setFilepath(filePath);
        int i = teFileuploadMapper.updateTeFileupload(teFileupload);
        if (i != 1) {
            throw new CustomException("文件上传失败!");
        }
    }
    return "文件上传成功";
}

/***
 * 附件下载
 * @param id
 * @param response
 * @return
 */
@Override
public void downloadFile(Long id, HttpServletResponse response, int funNo) {
    String filePathLast = "";

    if (funNo == 1020) {//实验室认可和资质
        LaboratoryFileupload laboratoryFileupload = laboratoryFileuploadMapper.selectLaboratoryFileuploadByLfid(id);
        if (StringUtils.isEmpty(laboratoryFileupload.getFilepath())) {
            throw new CustomException("附件不存在,请联系管理员!");
        }
        filePathLast = laboratoryFileupload.getFilepath();
    } else if (funNo == 1030) {//能力提供者
        PtpFileupload ptpFileupload = ptpFileuploadMapper.selectPtpFileuploadByLfid(id);
        if (StringUtils.isEmpty(ptpFileupload.getFilepath())) {
            throw new CustomException("附件不存在,请联系管理员!");
        }
        filePathLast = ptpFileupload.getFilepath();
    } else if (funNo == 1040) {//常用文件
        TeFileupload teFileupload = teFileuploadMapper.selectTeFileuploadByFileuploadid(id);
        if (StringUtils.isEmpty(teFileupload.getFilepath())) {
            throw new CustomException("附件不存在,请联系管理员!");
        }
        filePathLast = teFileupload.getFilepath();
    }


    UploadConfig uploadConfig = ApplicationContextUtils.getBean(UploadConfig.class);
    String rootUploadPath = uploadConfig.getRootUploadPath();
    String filePath = rootUploadPath + filePathLast;
    String downloadPath = filePath;
    // 下载名称
    String downloadName = StringUtils.substringAfterLast(downloadPath, "/");

    if ( filePathLast.contains(".doc") || filePathLast.contains(".docx")) {
        SysConfig sysConfig = new SysConfig();
        sysConfig.setConfigKey("sys.path");
        List<SysConfig> sysConfigList = sysConfigMapper.selectConfigList(sysConfig);
        if (sysConfigList.size() < 1) {
            throw new CustomException("数据库本地文件预览路径没有配置,请联系管理员!");
        }

        SysConfig sysConfig1 = new SysConfig();
        sysConfig1.setConfigKey("filePath");
        List<SysConfig> sysConfig1List = sysConfigMapper.selectConfigList(sysConfig1);
        if (sysConfig1List.size() < 1) {
            throw new CustomException("物理文件不存在,请联系管理员!");
        }

        String configValue = sysConfigList.get(0).getConfigValue();
        //临时文件路径
        String tempPath = configValue + filePathLast;
        Word2PdfAsposeUtil.doc2pdf(sysConfig1List.get(0).getConfigValue() + filePathLast, configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf");
        downloadPath = configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf";
        downloadName = filePathLast.substring(0, filePathLast.length()-4)+".pdf";
    }


    if (filePathLast.contains(".xls") || filePathLast.contains(".xlsx") ) {
        SysConfig sysConfig = new SysConfig();
        sysConfig.setConfigKey("sys.path");
        List<SysConfig> sysConfigList = sysConfigMapper.selectConfigList(sysConfig);
        if (sysConfigList.size() < 1) {
            throw new CustomException("数据库本地文件预览路径没有配置,请联系管理员!");
        }

        SysConfig sysConfig1 = new SysConfig();
        sysConfig1.setConfigKey("filePath");
        List<SysConfig> sysConfig1List = sysConfigMapper.selectConfigList(sysConfig1);
        if (sysConfig1List.size() < 1) {
            throw new CustomException("物理文件不存在,请联系管理员!");
        }

        String configValue = sysConfigList.get(0).getConfigValue();
        //临时文件路径
        String tempPath = configValue + filePathLast;
        Word2PdfAsposeUtil.doc2pdf(sysConfig1List.get(0).getConfigValue() + filePathLast, configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf");
        Word2PdfAsposeUtil.excel2pdf(sysConfig1List.get(0).getConfigValue() + filePathLast, configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf");
        downloadPath = configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf";
        downloadName = filePathLast.substring(0, filePathLast.length()-4)+".pdf";
    }

    if (filePathLast.contains(".txt")) {
        SysConfig sysConfig = new SysConfig();
        sysConfig.setConfigKey("sys.path");
        List<SysConfig> sysConfigList = sysConfigMapper.selectConfigList(sysConfig);
        if (sysConfigList.size() < 1) {
            throw new CustomException("数据库本地文件预览路径没有配置,请联系管理员!");
        }

        SysConfig sysConfig1 = new SysConfig();
        sysConfig1.setConfigKey("filePath");
        List<SysConfig> sysConfig1List = sysConfigMapper.selectConfigList(sysConfig1);
        if (sysConfig1List.size() < 1) {
            throw new CustomException("物理文件不存在,请联系管理员!");
        }

        String configValue = sysConfigList.get(0).getConfigValue();
        //临时文件路径
        String tempPath = configValue + filePathLast;
        Word2PdfAsposeUtil.doc2pdf(sysConfig1List.get(0).getConfigValue() + filePathLast, configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf");
        Word2PdfAsposeUtil.excel2pdf(sysConfig1List.get(0).getConfigValue() + filePathLast, configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf");
        downloadPath = configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf";
        downloadName = filePathLast.substring(0, filePathLast.length()-4)+".pdf";
    }

    if ( filePathLast.contains(".sql")) {
        SysConfig sysConfig = new SysConfig();
        sysConfig.setConfigKey("sys.path");
        List<SysConfig> sysConfigList = sysConfigMapper.selectConfigList(sysConfig);
        if (sysConfigList.size() < 1) {
            throw new CustomException("数据库本地文件预览路径没有配置,请联系管理员!");
        }

        SysConfig sysConfig1 = new SysConfig();
        sysConfig1.setConfigKey("filePath");
        List<SysConfig> sysConfig1List = sysConfigMapper.selectConfigList(sysConfig1);
        if (sysConfig1List.size() < 1) {
            throw new CustomException("物理文件不存在,请联系管理员!");
        }

        String configValue = sysConfigList.get(0).getConfigValue();
        //临时文件路径
        String tempPath = configValue + filePathLast;

        Word2PdfAsposeUtil word2PdfAsposeUtil = new Word2PdfAsposeUtil();

        word2PdfAsposeUtil.wordAndTextToPdf(sysConfig1List.get(0).getConfigValue() + filePathLast, configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf");
        downloadPath = configValue + filePathLast.substring(0, filePathLast.length()-4)+".pdf";
        downloadName = filePathLast.substring(0, filePathLast.length()-4)+".pdf";
    }
    try {
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        FileUtils.setAttachmentResponseHeader(response, downloadName);
        FileUtils.writeBytes(downloadPath, response.getOutputStream());
    } catch (Exception e) {
        throw new CustomException("下载失败!");
    }

}

}

}
所需jar
image.png
maven

com.jcraft
aspose-words
1.25

    <dependency>
        <groupId>com.jcraft</groupId>
        <artifactId>aspose-cells</artifactId>
        <version>1.25</version>
    </dependency>
相关文章
|
13天前
|
Python
办公自动化-Python如何提取Word标题并保存到Excel中?
办公自动化-Python如何提取Word标题并保存到Excel中?
30 2
|
1天前
|
前端开发
React实现一个excel文件导出
React实现一个excel文件导出
4 0
|
1天前
使用LabVIEW打开默认应用程序中的文档(PDF,Word,Excel,Html)
使用LabVIEW的&quot;Open a Document on Disk.vi&quot;,存于&lt;LabVIEW&gt;\vi.lib\Platform\browser.llb,可让默认应用打开硬盘文档。此VI仅基础打开功能,高级控制推荐LabVIEW Report Generation Toolkit或ActiveX。注意:避免版本升级问题,最好将VI复制到vi.lib外的目录。
|
6天前
|
Java Apache
Java将word、excel文件转成pdf文件
【5月更文挑战第26天】Java将word、excel文件转成pdf文件
34 1
|
8天前
|
数据采集 数据挖掘 数据处理
Python数据分析实战:使用Pandas处理Excel文件
Python数据分析实战:使用Pandas处理Excel文件
81 0
|
20天前
|
Java Apache 索引
POI操作大全(动态合并单元格,为单元格生成一个自定义的数据显示格式,自定义公式计算结果生成,读取excel,word文件在生成图片,word指定位置生成图片)
POI操作大全(动态合并单元格,为单元格生成一个自定义的数据显示格式,自定义公式计算结果生成,读取excel,word文件在生成图片,word指定位置生成图片)
|
16天前
|
前端开发 Java
基于Java爬取微博数据(二) 正文长文本+导出数据Excel
【5月更文挑战第12天】基于Java爬取微博数据,正文长文本+导出数据Excel
|
21天前
|
Java
java导出复杂excel
java导出复杂excel
分享:2秒快速查询40万手机号码归属地,批量手机号码归属地查询可以导出excel表格,WPS表格查询手机号码归属地怎么操作,批量手机号码归属地批量查询软件,批量号码查询按省份和城市分类,按运移动号码电信号码联通号码分类整理
本文介绍了如何批量快速查询手机号码归属地并进行分类。首先,通过提供的百度网盘或腾讯云盘链接下载免费查询软件。其次,开启软件,启用复制粘贴功能,直接粘贴号码列表并选择高速查询。软件能在极短时间内(如1.76秒内)完成40多万个号码的查询,结果包括归属地、运营商、邮箱和区号,且数据准确。之后,可直接导出数据至表格,若数据超过100万,可按省份、城市及运营商分类导出。文章还附带了操作动画演示,展示全程流畅的处理大量手机号码归属地查询的过程。
分享:2秒快速查询40万手机号码归属地,批量手机号码归属地查询可以导出excel表格,WPS表格查询手机号码归属地怎么操作,批量手机号码归属地批量查询软件,批量号码查询按省份和城市分类,按运移动号码电信号码联通号码分类整理
|
10天前
|
文字识别
分享:如何ocr识别身份证复印件并导出至excel表格 ? 图片批量识别导出excel表格应用,图片批量识别转excel表格的方法
该软件是一款OCR身份证识别工具,能批量处理图片,自动提取身份证信息并导出为Excel。支持百度网盘和腾讯云盘下载。用户界面直观,操作简单,适合新手。识别过程包括:打开图片、一键识别、导出结果。特别注意,此程序仅适用于身份证识别,不适用于其他类型的图片识别。
分享:如何ocr识别身份证复印件并导出至excel表格 ? 图片批量识别导出excel表格应用,图片批量识别转excel表格的方法