使用设计模式基于easypoi优雅的设计通用excel导入功能

news2024/12/27 14:00:18

文章目录

    • 概要
    • 整体架构流程
    • 代码设计
      • 配置类
      • 通用API
      • 分发器
      • 处理器
      • 业务逻辑处理service接口
      • 策略模型
    • 小结

概要

基于java原生 + easypoi结合适配器模式、策略模式、工厂模式设计一个通用的excel导入框架

整体架构流程

在这里插入图片描述

代码设计

由上到下,分别讲解代码

配置类

ExcelConfigEnum
该配置类是声明导入业务类型,导入参数与Handler之间的实例化关系。

@Getter
public enum ExcelConfigEnum {
    // 测试配置
    TEST("test", "测试", "com.xxx.TestExcelHandler"),
    ;
    
    private String type;
    private String desc;
    private String importClazz;

	ExcelConfigEnum (String type, String desc, String importClazz) {
		// ... 全参构造函数
	}

	// 根据type获取enum
	public static ExcelConfigEnum getByType(String type) {
		// codes...
	}
}

ExcelPolicyConfiguration
该注解类用于配置handler对应的策略

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AsyncExcelPolicyConfiguration
/**
* 导入策略
*/
Class<?extends AsyncExcelPolicy>policy();
/**
* 分片大小
*/
int shardingNum()default 1000;
}


AsyncExcelTask
多线程调度任务类


@Sf4j
public class AsyncExcelTask implements Runnable{
	// 处理器
	private AsyncExcelHandler handler;

	AsyncExcelTask(AsyncExcelHandler handler){
		this.handler=handler;
	}
	@Override
	public void run(){handler.importExcel();}
}

Factory
handler工厂,用于实例化handler

public class ExcelHandlerFactory {

	public static AsyncExcelHandler getInstance(String type,InputStream in,Object param,String importId,String userId)
					throws NoSuchMethodException,IllegalAccessException,InvocationTargetException,InstantiationException,ClassNotFoundException {
	
		AsyncExcelConfigEnum moduleEnum = AsyncExcelConfigEnum.getByType(type);
		Class clz = Class.forName(moduleEnum.getImportclazz());
		if (Objects.isNuLl(clz)){
			return null;
		}
		AsyncExcelPolicyConfiguration annotation = (AsyncExcelPolicyConfiguration)clz.getAnnotation(AsyncExcelPolicyConfiguration.class);
		if (Objects.isNuLL(annotation)){
			log.error("缺失导入策略注解")throw new ServiceException((500,"缺失导入策略注解")}
		Class<?extends AsyncExcelPolicy>policyClass = annotation.policy();
		int shardingNum = annotation.shardingNum();
		AsyncExcelPolicy policy = policyClass.newInstance();
		Constructor<?extends AbstractAsyncExcelHandler> constructor =
		clz.getDeclaredConstructor(InputStream.class,Object.class,String.class,String.class,AsyncExcelPolicy.class,int.class);
		return constructor.newInstance(in,param,importId,userId,policy,shardingNum);
	}
}

通用API

@Api(va1ue="异形exce1守人守出",tags={"异步exce1手人子田")
@RestController
@RequestMapping("/common/asyncExcel")
public class AsyncExcelController {
	@Autowired
	private AuthUtils authUtils;
	
	private static final String MODULE_AME="异步exce1导入导出";
	
	@OperLogOption(module MODULE_NAME,oper OperLogOption.Oper.IMPORT,desc ="excel")
	@PostMapping("/import")
	public Boolean asyncImportExcel(ExcelImportParams excelImportParams){
	
		AsyncExcelDispatcher.asyncDispatch(excelImportParams.getMultipartFiles[0],excelImportParams.getType(),
											excelImportParams.getObject(),authUtils.currentUserId());
		return true;
	}
}

分发器

根据前段传递的type通过factory构建handler

@s1f4j
public class AsyncExcelDispatcher {

public static void asyncDispatch(MultipartFile file,String type,Object param,String userId){
	//构造异步导入excel记录对象
	UserAsyncExcel userAsyncExcel buildAsyncExceLEntity(file,userId);
	try {
		AsyncExcelConfigEnum excelEnum AsyncExcelConfigEnum.getByType(type);
		// 检查导入配置是否存在
		if (Objects.isNuLL(excelEnum) || StringUtils.isBLank(excelEnum.getImportClazz())){
			throw new ServiceException(BizExceptionCommonEnum.INTERFACE_NOT_EXSIT_ERROR);
		}
		// 保存导入信息,状态执行中
		IUserFeignService userFeignService Springutils.getBean(IUserFeignService.class);
		userFeignService.save(userAsyncExcel);
		//获取handLer实例
		InputStream in = file.getInputStream();
		AsyncExcelHandler handler = ExcelHandlerFactory.getInstance(type,in,param,userAsyncExcel.getId(),userId);
		ExecutorService executor ThreadPoolFactory.ThreadPoolEnum.IMPORT_EXCEL.getThreadPool();
		//异步提交任务
		AsyncExcelTask asyncExcelTask = new AsyncExcelTask(handler);
		executor.submit(asyncExcelTask);
	catch (IOException | InstantiationException | InvocationTargetException | NoSuchMethodException |	IllegalAccessException | ClassNotFoundException ex){
		log.error("获取实例失败",ex);
		//写入状态
		IUserFeignService userFeignService SpringUtils.getBean(IUserFeignService.class);
		userAsyncExcel.setStatus(AsyncExcelStatus.EXCEPTION);
		userAsyncExcel.setMessage(ex.getMessage());
		userFeignService.update(userAsyncExcel);
		throw new ServiceException(BizExceptionCommonEnum.INTERFACE_NOT_EXSIT_ERROR);
	}

	private static UserAsyncExcel buildAsyncExcelEntity(MultipartFile file,String userId){
		UserAsyncExceluserAsyncExcel new UserAsyncExcel();
		String id UuidUtils.getId();
		userAsyncExcel.setCreateTime(Timestamp.valueof(LocalDateTime.now(Clock.systemDefaultZone())));
		userAsyncExcel.setCreateUserId(userId);
		userAsyncExcel.setstatus(AsyncExcelStatus.EXECUTING);
		userAsyncExcel.setTitle(file.getoriginalFilename());
		userAsyncExcel.setType("I");
		userAsyncExcel.setId(id)j
		return userAsyncExcel;
	}


}

处理器

IAsyncExcelHandler
handler接口, 规范编码

public interface AsyncExcelHandler<T extends ExcelImportBaseEntity> {
	// 导入excel
	void importExcel();
	// 重置导入参数
	void resetImportParams();
	// 设置数据处理器
	void setDataHandler(IExcelDataHandler dataHandler);

	// 设置字典处理器
	void setDictHandler(IExcelDictHandler dictHandler);
	
	// 是否需要字段校验
	void needverify(boolean needverify);

	// 获取业务服务对象
	IAsyncExcelService getservice();
}

AbstractAsyncExcelHandler
处理器抽象实现类

public abstract class AbstractAsyncExcelHandler<T extends ExcelImportBaseEntity> implements AsyncExcelHandler{
	/*
	*文件流
	*/
	protected InputStream in;
	/**
	*导入参数
	*/
	protected Object object;
	/*
	*导入id
	*/
	protected String importId;
	/*
	*导入用户
	*/
	protected String userId;
	/*
	*策略
	*/
	protected AsyncExcelPolicy policy;
	/*
	* 分片大小
	*/
	protected int shardingNum;
	/
	*
	* 导入设置参数
	*/
	protected ImportParams importParams new ImportParams();
	/*
	 *文件服务
	 */
	protected IFileFeignService fileFeignService=SpringUtils.getBean(IFileFeignService.class);
	/*
	* user服务
	*/
	protected IUserFeignService userFeignService SpringUtils.getBean(IUserFeignService .class);
	/*
	 * 构造函数
	*/
	public AbstractAsyncExcelHandler(InputStream in,Object object,String importId,String userId,AsyncExcelPolicy policy,int shardingNum){
		this.in = in;
		this.object = object;
		this.importId = importId;
		this.userId = userId;
		this.policypolicy = policy;
		this.shardingNum = shardingNum;
	}
	@Override
	public void importExcel(){
		//1导入之前设置参效
		resetImportParams();
		setDataHandler(new DefaultDataHandler());
		setDictHandler(new DefaultExcelDictHandler());
		needverify(true);
		
		ParameterizedType parameterizedType = (ParameterizedType) this.getclass().getGenericSuperclass();
		Class clazz = (Class) parameterizedType.getActualTypeArguments()[0];
		ExcelImportResult<T> result;
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ByteArrayInputStream bis = null;
		try {
			//复制一份流用于输出校验结果
			IOUtils.copy(in,bos);
			bis = new ByteArrayInputStream(bos.toByteArray());
			result = ExcelImportUtil.importExceLMore(bis,clazz,importParams);
		}
		catch (Exception ex) {
			IOUtils.closeQuietly(this.in);
			IOUtils.cLoseQuietly(bos);
			IOUtils.closeQuietLy(bis);
			log,error("excel解析失败“,ex);
			// 更新异常状态
			updateMessage4Exception(ex.getMessage());
			throw new ServiceException(BizExceptionCommonEnum.POI_ERROR)
		}
		
		//导入后处理
		//注解校验结哭
		if (result.isverifyFail()) {
			// 校验结果处理
			postverifyFailed(result,bos,bis);
			return;
		}
		// 校验是否实现service
		IAsyncExcelService service = getService();
		if (Objects.isNuLL(service)){
			Log.error("业务服务未返回service对象");
			//更新导入异常状态
			updateMessage4Exception("业务服务未实现");
			throw new ServiceException(500,"业务服务未实现");
		}
		// 业务校验
		service.verify(result);
		if (result.isVerifyFail()){
			//校验结果处理
			postverifyFailed(result,bos,bis);
			return;
		}
		//业务逻辑
		runPolicy(result.getList());
	}
	
	// 策略运行
	protected void runPolicy(List<T> list){
		try {
			policy.runPolicy(list,shardingNum,getService(),importId);
		} catch (Exception ex){
			//标识异常
			Log.error("业务数据插入异常。",e×);
			UserAsyncExcel userAsyncExcel new UserAsyncExcel();
			userAsyncExcel.setId(importId);
			userAsyncExcel.setstatus(AsyncExcelStatus.EXCEPTION);
			userAsyncExcel.setMessage("分片号入发生异常");
			userFeignService.update(userAsvncExcel):
		}
	}
	// 校验失败后置处理
	private void postVerifyFailed(ExcelImportResult<T> result,ByteArrayOutputstream bos,ByteArrayInputstream bis){
		try {
			List<T> failList = result.getFailList();
			bis.reset();
			Workbook workbook = WorkbookFactory.create(bis);
			Sheet sheet = workbook.getsheetAt(index:0);
			int titleRowNum = importParams.getTitleRows(>+importParams.getHeadRows();
			short lastCellNum = sheet.getRow(titleRowNum).getLastCellNum();
			CellStyle errorCellStyle = createCellStyle(workbook);
			for (T failEntity failList){
				Integer rowNum failEntity.getRowNum();
				Rowrow sheet.getRow(rowNum)j
				Cell cell row.createCell(lastCellNum);
				cell.setCellValue(failEntity.getErrorMsg());
				cell.setCellStyle(errorCellStyle);
			}
			
			workbook.getsheetAt(0).setColumnwidth(lastCellNum, 5000);
			UserAsyncExcel userAsyncExcel = userFeignService.getById(importId).getData();
			
			// 流清空,重用
			bos.flush();
			bos.reset();
			workbook.write(bos);
			Map<String,String> errFileMap = uploadVerifyFailedExcel(bos,userAsyncExcel.getTitle());
			// 更新导入结果为校验失败
			String fileId = String.join(StringPool.COMMA,errFileMap.keySet());
			userAsyncExcel.setstatus(AsyncExcelStatus.VERIFY_FATLED);
			userAsyncExcel.setResFileId(fileId);
			userFeignService.update(userAsyncExcel);
			}
		catch (IOException ex) {
			IOUtils.closeQuietly(bos);
			IOUtils.cLoseQuietly(bis);
			Log.error("设置校验结果异常",ex);
			/更新导入异常状态
			updateMessage4Exception(ex.getMessage());
			throw new ServiceException(5OB,"业务校验失败")} finally {
			Log.info("原始流复件关闭");
			IOUtils.cLoseQuietly(bos);
			IOUtils.cLoseQuietly(bis);
		}
	}

	private Map<String,String>uploadVerifyFailedExcel(ByteArrayOutputStream erroros, String fileName){
		long time System.currentTimeMiLLis();
		int idx fileName.lastIndexof(StringPool.DOT);
		String pre fileName.substring(0,idx);
		String suffer fileName.substring(idx);
		fileName pre time suffer;
		MultipartFile[]multipartFiles new MultipartFile[]{new MockMultipartFile(Constants.ExcelConstants.UPLOAD_FILE_FIELD,
		fileName,MediaType.MUL TIPART_FOR_DATA_VALUE,erroros.toByteArray())};
		return fileFeignService.uploadTemp(multipartFiles).getData();
	}
	
	protected Cellstyle createCellStyle(Workbook workbook){
		CellStyle errorCellStyle workbook.createCellstyle();
		Font font workbook.createFont();
		font.setColor(Font.COLOR_RED);
		errorCellStyle.setFont(font);
		// 背景色
		// 设置背景色填充方式为实线填充
		// errorCeLlStyle.setFiLLPattern(FillPatternType.SOLID_FOREGROUND);
		// errorceLlStyle.setFiLLForegroundcolor(IndexedColors.RED.getIndex());
		errorCellstyle.setWrapText(true);
		return errorCellStyle;
	}
	
	private void updateMessage4Exception(String message){
		UserAsyncExcel userAsyncExcel = new UserAsyncExcel();
		userAsyncExcel.setId(importId);
		userAsyncExcel.setStatus(AsyncExcelStatus.EXCEPTION);
		userAsyncExcel.setMessage(message);
		userFeignService.update(userAsyncExcel);
	}
@Override
public void resetImportParams(){
	//设置标题行数
	this.importParams.setTitleRows(1);
	//设置表头行数
	this.importParams.setHeadRows(2);
}

	@Override
	public void setDataHandler(IExcelDataHandler dataHandler){this.importParams.setDataHandler(dataHandler)};
	@Override
	public void setDictHandler(IExcelDictHandler dictHandler){this.importParams.setDictHandler(dictHandler)};
	@Override
	public void needverify(boolean needVerify){this.importParams.setNeedVerify(needVerify)};


}


业务逻辑处理service接口

IAsyncExcelService

public interface IAsyncExcelService<T extends ExcelImportBaseEntity>{
	/*
	* 业务校验
	* @param result
	*/
	void verify(ExcelImportResult<T> result);
	/**
	* 处理导入数据
	*
	*/
	void dealResultData(List<T> list);
}

策略模型

AsyncExcelPolicy 策略接口
规范策略实现

public interface IAsyncExcelService<T extends ExcelImportBaseEntity> {
	/**
	* 业务校验
	*/
	void verify(ExcelImportResult<T> result);
	/**
	* 处理导入数据
	*/
	void dealResultData(List<T> list);
}

ShardingPolicy分片策略

Public class phardangPolicy implements AsyncExcelPolicy{

	@Override
	publie void runPolicy(List list,int shardingNun,IAsyncExcelService service,String impertId){
		//数据分片
		NongoTerplate mongoTeplate = Springutils.getBean(NangoTerplate.class);
		List<List> splitList = Listutil.splitlist(shardinghun,list);
		List<AsyncExcelShardingEntity> nongoEntityList = splitList.streas().map(v->{
			AsyncExcelShardingEntity entity new AsyncExcelshardingEntity(
			entity.setImportId(impertId);
			entity.setshardingId(Uuidutils.getId());
			entity.setSucceeded(falae);
			entity.setData(v);
			return entity;
		}).collect(Collectors.tolist());
		// 分片数据存入mongo
		mongoTerplate.insertAll(mongoEntityList);
		Log.info("分片数据:{}", ongoEntitylist);
		//分片进入业务导入方法
		for (AsyncExcelShardingEntity entity mangofntitylist){
			List subList = (List) entity.gotData();
			service.dealResultData(subList);
			// 执行成功成功删除数据
			mangoTenplate.remove(entity);
		}
	}
}

小结

业务服务使用只需要三步

  1. 配置对应的handler权限定名。
  2. 实现handler类,根据自己的excel样式设置参数,用于解析excel数据。
  3. 实现service服务接口,实现校验数据方法和导入方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1144965.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【IO面试题 三】、说说NIO的实现原理

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;说说NIO的实现原理 参…

eclipse安装教程(2021版)

第一步&#xff1a;下载JDK &#xff08;下载地址&#xff09; Java SE - Downloads 第二步 根据自己电脑的系统&#xff0c;选择相应的版本x64代表64位&#xff0c;x86代表32位。点击相应的JDK进行下载 点击之后会出现一个对话框 同意之后下载。(记住下载到哪&#xff0c;打…

二叉树:什么样的二叉树适合用数组来存储?

文章来源于极客时间前google工程师−王争专栏。 前面我们讲的都是线性表结构&#xff0c;栈、队列等等。今天我们讲一种非线性表结构&#xff0c;树。树这种数据结构比线性表的数据结构要复杂得多&#xff0c;内容也比较多&#xff0c;所以我会分四节来讲解。 问题&#xff1…

【Javascript】弹出框

目录 警告框 确认框 提示框 警告框 alert(你好); 确认框 var isConfirm confirm(请确认) console.log( isConfirm); 提示框

基于非侵入式负荷检测与分解的电力数据挖掘

基于非侵入式负荷检测与分解的电力数据挖掘 在这里插入图片描述 **摘要&#xff1a;本案例将根据已收集到的电力数据&#xff0c;深度挖掘各电力设备的电流、电压和功率等情况&#xff0c;分析各电力设备的实际用电量&#xff0c;进而为电力公司制定电能能源策略提供一定的参…

电脑报错由于找不到vcruntime140.dll文件怎么修复

VCruntime140.dll是一个重要的动态链接库文件&#xff0c;它对于许多应用程序的运行起着关键作用。如果计算机中丢失了这个文件&#xff0c;可能会导致一些程序无法正常启动或运行&#xff0c;从而影响到用户的正常使用。在本文中&#xff0c;我们将详细介绍vcruntime140.dll文…

Cross Site Scripting (XSS)

攻击者会给网站发送可疑的脚本&#xff0c;可以获取浏览器保存的网站cookie&#xff0c; session tokens, 或者其他敏感的信息&#xff0c;甚至可以重写HTML页面的内容。 背景 XSS漏洞有不同类型&#xff0c;最开始发现的是存储型XSS和反射型XSS&#xff0c;2005&#xff0c;Am…

【JVM】字节码文件的组成部分

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 JVM 一、字节码文件的组成部分1.1 iconst_0…

LabVIEW开发TDS1000 和TDS2000 系列泰克示波器

LabVIEW开发TDS1000 和TDS2000 系列泰克示波器 泰克示波器是经常用到的工具&#xff0c;一般手动操作即可&#xff0c;但有时候也要集成到系统中&#xff0c;需要程控。这时候先要下载厂家提供的例子&#xff0c;了解LabVIEW的demo。根据不用的示波器型号&#xff0c;选择和计…

在职场上有多少人输在了不会用Python数据分析

在职场上有多少人输在了不会用Python数据分析 在职场上有多少人输在了不会用Python数据分析引言方向一&#xff1a;学了Python能做什么&#xff1f;方向二&#xff1a;Python的应用领域1. Web开发&#xff1a;2. 自动化和脚本编写&#xff1a;3. 数据科学和分析&#xff1a;4. …

锐捷RG-UAC账号密码信息泄露

第一种方法&#xff1a; 构造如下Payloads https://xxxxx/get_dkey.php?useradmin访问payload成功后&#xff0c;可以看到受影响系统的超级管理员权限账号、访客权限账号、审计权限账号的账户名和 MD5 加密的密码值&#xff0c;如下图所示&#xff1a; 下面可以使用上一步查…

【强化学习】10 —— DQN算法

文章目录 深度强化学习价值和策略近似RL与DL结合产生的问题深度强化学习的分类 Q-learning回顾深度Q网络&#xff08;DQN&#xff09;经验回放优先经验回放 目标网络算法流程 代码实践CartPole环境代码结果 参考 深度强化学习 价值和策略近似 我们可以利用深度神经网络建立这些…

缺少d3dx9_43.dll怎么解决 win系统如何运行dll文件?

大家好&#xff01;今天我来给大家分享一下关于d3dx9_43.dll缺失的4种详细解决方案。 首先&#xff0c;让我们了解一下d3dx9_43.dll是什么文件。其实&#xff0c;d3dx9_43.dll是DirectX的一个组件&#xff0c;它主要负责处理游戏中的一些特效和动画效果。如果这个文件丢失了&a…

MacOS系统电脑怎么彻底清理系统垃圾注册表App Cleaner可以深度清理吗

App Cleaner & Uninstaller 是一款适用于 Mac 操作系统的软件应用程序&#xff0c;允许用户轻松卸载不需要的应用程序、删除剩余文件和文件夹以及管理启动项。该应用程序会分析与您要删除的应用程序关联的文件&#xff0c;并帮助识别其所有组件&#xff0c;以便您可以一次将…

C++学习day--23 枚举、类型定义、头文件

1、枚举 1.1 枚举的概念 枚举是 C/C 语言中的一种基本数据类型&#xff0c; 它可以用于声明一组常数 。当一个变量有几个固 定的可能取值时&#xff0c;可以将这个变量定义为枚举类型。 比如&#xff0c;你可以用一个枚举类型的变量来表示季节&#xff0c;因为季节只有 4 …

C++之左值、右值、std::forward、std::move总结(二百五十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

033-第三代软件开发-固定区域截图

第三代软件开发-固定区域截图 文章目录 第三代软件开发-固定区域截图项目介绍固定区域截图QWidget 版本QML 版本 自由截图自由截图二 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QM…

粤嵌实训医疗项目--day03(Vue + SpringBoot)

往期回顾 粤嵌实训医疗项目day02&#xff08;Vue SpringBoot&#xff09;-CSDN博客 粤嵌实训医疗项目--day01&#xff08;VueSpringBoot&#xff09;-CSDN博客 目录 一、SpringBoot AOP的使用 二、用户模块-注册功能&#xff08;文件上传&#xff09; 三、用户模块-注册实现…

基于stm32的ADC读取烟雾报警器的数值

本文想要设计一个设计一个有stm32控制的烟雾报警系统。通过MQ-2烟雾报警器将获取模拟的数值传递给stm32的ADC外设并在串口助手上显示对应的电压值。烟雾报警器浓度越高&#xff0c;他的电压就越高&#xff0c;但是不会超过3.3V。设置一个电压临界值&#xff0c;当传输回来的电压…

Python环境下LaTeX数学公式转图像方案调研与探讨

目录 引言方案一&#xff1a;基于LaTeX环境方案二&#xff1a;基于KaTeX(推荐) 方案三&#xff1a;基于Matplotlib写在最后 引言 近来&#xff0c;涉及到一些公式识别的项目&#xff0c;输入是公式的图像&#xff0c;输出是LaTeX格式的数学公式字符串。 这类项目一般都采用深…