基于mybatisPlus+Easycode+mapstruct实现的代码生成器 您所在的位置:网站首页 idea代码生成器 基于mybatisPlus+Easycode+mapstruct实现的代码生成器

基于mybatisPlus+Easycode+mapstruct实现的代码生成器

2023-09-18 19:34| 来源: 网络整理| 查看: 265

基于mybatisPlus+Easycode+mapstruct实现的代码生成器 需求出发点

1.使用mybatis plus的原生代码生成器,很不方便,需要引入模板引擎,还有生成器依赖,而且生成的代码,会有些你不想要的东西,再去处理也很麻烦。

2.对PO,VO,DTO的相互转换我们一般使用的是BeanUtils,在接触过mapstruct后,决定使用mapstruct来替换BeanUtils的相互拷贝。

基本概念 1.Mybatis 和 Mybatis Plus 的区别 MyBatis: 所有SQL语句全部自己写手动解析实体关系映射转换为MyBatis内部对象注入容器不支持Lambda形式调用 Mybatis Plus: 强大的条件构造器,满足各类使用需求内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作支持Lambda形式调用提供了基本的CRUD功能,连SQL语句都不需要编写自动解析实体关系映射转换为MyBatis内部对象注入容器 2.EasyCode是什么?

EasyCode是基于IntelliJ IDEA开发的代码生成插件,支持自定义任意模板(Java,html,js,xml)。只要是与数据库相关的代码都可以通过自定义模板来生成。支持数据库类型与java类型映射关系配置。支持同时生成生成多张表的代码。每张表有独立的配置信息。完全的个性化定义,规则支持自定义设置.

3.PO、DTO和VO

PO:persistent object 持久对象,对应数据库中的一条记录。

VO:view object 表现层对象,最终返回给前端的对象。

DTO:data transfer object数据传输对象,如dubbo服务之间传输的对象。

4.mapstruct的优点 与手工编写映射代码相比: MapStruct通过生成冗长且容易出错的代码来节省时间。 与动态映射框架相比: 效率更高:使用简单的Java方法调用代替反射;编译时类型安全:只能映射相同名称或带映射标记的属性;编译时产生错误报告:如果映射不完整(存在未被映射的目标属性)或映射不正确(找不到合适的映射方法或类型转换)则会在编译时抛出异常。 mapstruct 对于实体转换的一些操作 package com.alan.springmapstruct.mapstructdemo.mapper; import com.alan.springmapstruct.mapstructdemo.bean.constants.StatusEnum; import com.alan.springmapstruct.mapstructdemo.bean.dto.OrderDto; import com.alan.springmapstruct.mapstructdemo.bean.po.OrderPo; import com.alan.springmapstruct.mapstructdemo.bean.vo.OrderVo; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; import java.util.List; /** * @author alan */ @Mapper(imports={StatusEnum.class}) public interface OrderMapper { OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class); @Mappings({ //对时间类型不同进行处理 @Mapping(source = "created", target = "createdTime", dateFormat = "yyyy-MM-dd HH:mm:ss"), //对枚举进行key-value转换(使用自定义方法对属性进行自定义处理) @Mapping(target = "status", expression = "java(StatusEnum.getDesc(orderPo.getStatus()))"), //自定义字段转换(添加或者字节处理) @Mapping(target = "orderId", expression = "java(OrderMapper.handlerOrderId(orderPo.getOrderId()))") }) OrderDto po2Dto(OrderPo orderPo); OrderPo dto2Po(OrderDto orderDto); OrderVo dto2Vo(OrderDto orderDto); OrderDto vo2Dto(OrderVo orderVo); /** * 对实体对象集合进行转换 * @param orderPoList * @return */ List orderPOs2OrderDTOs(List orderPoList); static String handlerOrderId(String orderId){ return "OID_"+orderId; } /** * 对转换进行自定义处理, * 切忌:如果该mapper内有collection相关的转换,必须保证只有一个对象对象转换的方法 */ /*default OrderDto orderPO2OrderDTO(OrderPo orderPo) { if (orderPo == null) { return null; } OrderDto orderDto = new OrderDto(); orderDto.setBuyerId(orderPo.getBuyerId()); orderDto.setStatus(StatusEnum.getDesc(orderPo.getStatus())); return orderDto; }*/ } 代码生成后的预览

在这里插入图片描述

快速开始

这次我们的脚手架使用了MybatisPlus,EasyCode,mapstruct,swagger2来生成CRUD的标准代码。

1.在pom文件中加入依赖 4.0.0 org.springframework.boot spring-boot-starter-parent 2.3.5.RELEASE com.alan.springmapstruct mapstructdemo 0.0.1-SNAPSHOT mapstructdemo Demo project for Spring Boot 1.8 1.4.1.Final 1.18.12 8.0.20 1.1.16 3.3.0 2.0.2 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web mysql mysql-connector-java ${mysql-connector-java.version} com.alibaba druid-spring-boot-starter ${druid.version} com.baomidou mybatis-plus-boot-starter ${mybatis-plus.version} org.projectlombok lombok ${org.projectlombok.version} provided org.mapstruct mapstruct ${org.mapstruct.version} com.github.xiaoymin knife4j-spring-boot-starter ${knife4j-spring-boot-starter.version} org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-compiler-plugin 3.8.1 1.8 1.8 org.projectlombok lombok ${org.projectlombok.version} org.mapstruct mapstruct-processor ${org.mapstruct.version} 2.在yml文件中添加链接配置 server: port: 9999 #mysql spring: datasource: # 数据库配置 url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/xxxxxx?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&maxReconnects=10 username: xxxxxx password: xxxxxx driverClassName: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 数据库连接池 druid: #初始化大小,最小,最大 initialSize: 10 minIdle: 1 maxActive: 50 # 配置获取连接等待超时的时间 maxWait: 60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 #配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 30000 validationQuery: select?'x' testWhileIdle: true testOnBorrow: false testOnReturn: false #打开PSCache,并且指定每个连接上PSCache的大小 poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 filters: stat,wall,slf4j #通过connectProperties属性来打开mergeSql功能;慢SQL记录 connectionProperties: druid.stargeSql=true;druid.stat.slowSqlMillis=5000 #mybatis-plus mybatis-plus: typeAliasesPackage: com.alan.springmapstruct.mapstructdemo mapper-locations: classpath:mappers/*.xml global-config: #刷新mapper 调试神器 db-config: #主键类型 0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID"; id-type: INPUT #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断" field-strategy: 2 #驼峰下划线转换 column-underline: false #数据库大写下划线转换 #capital-mode: true #逻辑删除配置 logic-delete-value: 1 logic-not-delete-value: 0 refresh: true configuration: #配置返回数据库(column下划线命名&&返回java实体是驼峰命名),自动匹配无需as(没开启这个,SQL需要写as: select user_id as userId) map-underscore-to-camel-case: true cache-enabled: false #配置JdbcTypeForNull, oracle数据库必须配置 jdbc-type-for-null: 'null' log-impl: org.apache.ibatis.logging.stdout.StdOutImpl database-id: mysql 3.idea中安装EasyCode插件并录入配置信息

1.在plugin中安装EasyCode插件

2.在Settings找到EasyCode开始配置,第一步先修改作者的信息,并可以导入配置,目前上传的配置只支持保存7天,在上传配置后会返回一个token,输入token即刻全部导入。 在这里插入图片描述 3.根据数据库的表结构完善映射关系 在这里插入图片描述 4.新建一个分组并拷贝模板文件 在这里插入图片描述 下面给出模板文件的详细信息,依次为PO,DTO,VO,modelMapper

PO模板 ##引入宏定义 $!init $!define ##使用宏定义设置回调(保存位置与文件后缀) #save("/model/po", "Po.java") ##使用宏定义设置包后缀 #setPackageSuffix("model.po") ##使用全局变量实现默认包导入 $!autoImport import java.io.Serializable; import com.baomidou.mybatisplus.extension.activerecord.Model; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import lombok.Data; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import lombok.Builder; ##使用宏定义实现类注释信息 #tableComment("实体类") @Data @Builder @AllArgsConstructor @NoArgsConstructor @TableName("$tableInfo.obj.name") public class $!{tableInfo.name}Po extends Model implements Serializable { private static final long serialVersionUID = $!tool.serial(); #foreach($column in $tableInfo.fullColumn) #if(${column.comment})/** * ${column.comment} */#end #if(${column.comment}=="自增主键") @TableId(value="PK_ID")#end #if(${column.comment}=="逻辑删除标记(0:正常,1:已删除)") @TableLogic(value = "0",delval = "1")#end private $!{tool.getClsNameByFullName($column.type)} $!{column.name}; #end ## 因为使用了lombok,所以去掉get和set方法的生成 #*#foreach($column in $tableInfo.fullColumn) ##使用宏定义实现get,set方法 #getSetMethod($column) #end*# } DTO模板 ##引入宏定义 $!init $!define ##使用宏定义设置回调(保存位置与文件后缀) #save("/model/dto", "Dto.java") ##使用宏定义设置包后缀 #setPackageSuffix("model.dto") ##使用全局变量实现默认包导入 $!autoImport import java.io.Serializable; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.Data; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import lombok.Builder; ##使用宏定义实现类注释信息 #tableComment("实体类") @Data @Builder @AllArgsConstructor @NoArgsConstructor public class $!{tableInfo.name}Dto extends Model implements Serializable { private static final long serialVersionUID = $!tool.serial(); #foreach($column in $tableInfo.fullColumn) #if(${column.comment})/** * ${column.comment} */#end private $!{tool.getClsNameByFullName($column.type)} $!{column.name}; #end ## 因为使用了lombok,所以去掉get和set方法的生成 #*#foreach($column in $tableInfo.fullColumn) ##使用宏定义实现get,set方法 #getSetMethod($column) #end*# } VO模板 ##引入宏定义 $!init $!define ##使用宏定义设置回调(保存位置与文件后缀) #save("/model/vo", "Vo.java") ##使用宏定义设置包后缀 #setPackageSuffix("model.vo") ##使用全局变量实现默认包导入 $!autoImport import java.io.Serializable; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import lombok.Builder; ##使用宏定义实现类注释信息 #tableComment("实体类") @Data @Builder @AllArgsConstructor @NoArgsConstructor @ApiModel(description = "信息类") public class $!{tableInfo.name}Vo extends Model implements Serializable { private static final long serialVersionUID = $!tool.serial(); #foreach($column in $tableInfo.fullColumn) #if(${column.comment})/** * ${column.comment} */#end @ApiModelProperty("$column.comment") private $!{tool.getClsNameByFullName($column.type)} $!{column.name}; #end ## 因为使用了lombok,所以去掉get和set方法的生成 #*#foreach($column in $tableInfo.fullColumn) ##使用宏定义实现get,set方法 #getSetMethod($column) #end*# } modelMapper模板 ##引入宏定义 $!init $!define ##使用宏定义设置回调(保存位置与文件后缀) #save("/model/mapper", "ModelMapper.java") ##使用宏定义设置包后缀 #setPackageSuffix("model.mapper") ##使用全局变量实现默认包导入 $!autoImport import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; import java.util.List; import $!{tableInfo.savePackageName}.model.po.$!{tableInfo.name}Po; import $!{tableInfo.savePackageName}.model.dto.$!{tableInfo.name}Dto; import $!{tableInfo.savePackageName}.model.po.$!{tableInfo.name}Po; import $!{tableInfo.savePackageName}.model.vo.$!{tableInfo.name}Vo; /** * $!{tableInfo.comment}($!{tableInfo.name})实体转换工具类 * * @author $!author * @since $!time.currTime() */ @Mapper public interface $!{tableInfo.name}ModelMapper { $!{tableInfo.name}ModelMapper INSTANCE = Mappers.getMapper($!{tableInfo.name}ModelMapper.class); $!{tableInfo.name}Dto changePO2DTO($!{tableInfo.name}Po $!tool.firstLowerCase($!{tableInfo.name})Po); $!{tableInfo.name}Po changeDTO2PO($!{tableInfo.name}Dto $!tool.firstLowerCase($!{tableInfo.name})Dto); $!{tableInfo.name}Vo changeDTO2VO($!{tableInfo.name}Dto $!tool.firstLowerCase($!{tableInfo.name})Dto); $!{tableInfo.name}Dto changeVO2DTO($!{tableInfo.name}Vo $!tool.firstLowerCase($!{tableInfo.name})Vo); List $!{tool.firstLowerCase($!{tableInfo.name})}POs2$!{tableInfo.name}DTOs(List $!{tool.firstLowerCase($!{tableInfo.name})}PoList); List $!{tool.firstLowerCase($!{tableInfo.name})}DTOs2$!{tableInfo.name}POs(List $!{tool.firstLowerCase($!{tableInfo.name})}DtoList); List $!{tool.firstLowerCase($!{tableInfo.name})}VOs2$!{tableInfo.name}DTOs(List $!{tool.firstLowerCase($!{tableInfo.name})}VoList); List $!{tool.firstLowerCase($!{tableInfo.name})}DTOs2$!{tableInfo.name}VOs(List $!{tool.firstLowerCase($!{tableInfo.name})}DtoList); } mapper/DAO模板 ##定义初始变量 #set($tableName = $tool.append($tableInfo.name, "Mapper")) ##设置回调 $!callback.setFileName($tool.append($tableName, ".java")) $!callback.setSavePath($tool.append($tableInfo.savePath, "/dao")) ##拿到主键 #if(!$tableInfo.pkColumn.isEmpty()) #set($pk = $tableInfo.pkColumn.get(0)) #end #if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}dao; import $!{tableInfo.savePackageName}.model.po.$!{tableInfo.name}Po; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; /** * $!{tableInfo.comment}($!{tableInfo.name})表数据库访问层 * * @author $!author * @since $!time.currTime() */ @Mapper public interface $!{tableName} extends BaseMapper{ } service模板 ##定义初始变量 #set($tableName = $tool.append($tableInfo.name, "Service")) ##设置回调 $!callback.setFileName($tool.append($tableName, ".java")) $!callback.setSavePath($tool.append($tableInfo.savePath, "/service")) ##拿到主键 #if(!$tableInfo.pkColumn.isEmpty()) #set($pk = $tableInfo.pkColumn.get(0)) #end #if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}service; import $!{tableInfo.savePackageName}.model.dto.$!{tableInfo.name}Dto; import java.util.List; /** * $!{tableInfo.comment}($!{tableInfo.name})表服务接口 * * @author $!author * @since $!time.currTime() */ public interface $!{tableName} { /** * 通过ID查询单条数据 * * @param $!pk.name 主键 * @return 实例对象 */ $!{tableInfo.name}Dto queryById($!pk.shortType $!pk.name); /** * 查询多条数据 * * @param offset 查询起始位置 * @param limit 查询条数 * @return 对象列表 */ List queryAllByLimit(int offset, int limit); /** * 新增数据 * * @param $!tool.firstLowerCase($!{tableInfo.name})Dto 实例对象 * @return 实例对象 */ $!{tableInfo.name}Dto insert($!{tableInfo.name}Dto $!tool.firstLowerCase($!{tableInfo.name})Dto); /** * 修改数据 * * @param $!tool.firstLowerCase($!{tableInfo.name})Dto 实例对象 * @return 实例对象 */ $!{tableInfo.name}Dto update($!{tableInfo.name}Dto $!tool.firstLowerCase($!{tableInfo.name})Dto); /** * 通过主键删除数据 * * @param $!pk.name 主键 * @return 是否成功 */ boolean deleteById($!pk.shortType $!pk.name); } serviceImpl模板 ##定义初始变量 #set($tableName = $tool.append($tableInfo.name, "ServiceImpl")) ##设置回调 $!callback.setFileName($tool.append($tableName, ".java")) $!callback.setSavePath($tool.append($tableInfo.savePath, "/service/impl")) ##拿到主键 #if(!$tableInfo.pkColumn.isEmpty()) #set($pk = $tableInfo.pkColumn.get(0)) #end #if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}service.impl; import $!{tableInfo.savePackageName}.model.po.$!{tableInfo.name}Po; import $!{tableInfo.savePackageName}.model.dto.$!{tableInfo.name}Dto; import $!{tableInfo.savePackageName}.model.mapper.$!{tableInfo.name}ModelMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.metadata.IPage; import $!{tableInfo.savePackageName}.dao.$!{tableInfo.name}Mapper; import $!{tableInfo.savePackageName}.service.$!{tableInfo.name}Service; import org.springframework.stereotype.Service; import org.springframework.beans.BeanUtils; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; /** * $!{tableInfo.comment}($!{tableInfo.name})表服务实现类 * * @author $!author * @since $!time.currTime() */ @Service("$!tool.firstLowerCase($!{tableInfo.name})Service") public class $!{tableName} implements $!{tableInfo.name}Service { @Resource private $!{tableInfo.name}Mapper $!tool.firstLowerCase($!{tableInfo.name})Mapper; /** * 通过ID查询单条数据 * * @param $!pk.name 主键 * @return 实例对象 */ @Override public $!{tableInfo.name}Dto queryById($!pk.shortType $!pk.name) { return $!{tableInfo.name}ModelMapper.INSTANCE.changePO2DTO(this.$!{tool.firstLowerCase($!{tableInfo.name})}Mapper.selectById($!pk.name)); } /** * 查询多条数据 * * @param offset 查询起始位置 * @param limit 查询条数 * @return 对象列表 */ @Override public List queryAllByLimit(int offset, int limit) { IPage selectPage = new Page(offset,limit); IPage page = this.$!{tool.firstLowerCase($!{tableInfo.name})}Mapper.selectPage(selectPage, null); return $!{tableInfo.name}ModelMapper.INSTANCE.$!{tool.firstLowerCase($!{tableInfo.name})}POs2$!{tableInfo.name}DTOs(page.getRecords()); } /** * 新增数据 * * @param $!tool.firstLowerCase($!{tableInfo.name})Dto 实例对象 * @return 实例对象 */ @Override public $!{tableInfo.name}Dto insert($!{tableInfo.name}Dto $!tool.firstLowerCase($!{tableInfo.name})Dto) { this.$!{tool.firstLowerCase($!{tableInfo.name})}Mapper.insert($!{tableInfo.name}ModelMapper.INSTANCE.changeDTO2PO($!tool.firstLowerCase($!{tableInfo.name})Dto)); return $!tool.firstLowerCase($!{tableInfo.name})Dto; } /** * 修改数据 * * @param $!tool.firstLowerCase($!{tableInfo.name})Dto 实例对象 * @return 实例对象 */ @Override public $!{tableInfo.name}Dto update($!{tableInfo.name}Dto $!tool.firstLowerCase($!{tableInfo.name})Dto) { this.$!{tool.firstLowerCase($!{tableInfo.name})}Mapper.updateById($!{tableInfo.name}ModelMapper.INSTANCE.changeDTO2PO($!tool.firstLowerCase($!{tableInfo.name})Dto)); return this.queryById($!{tool.firstLowerCase($!{tableInfo.name})}Dto.get$!tool.firstUpperCase($pk.name)()); } /** * 通过主键删除数据 * * @param $!pk.name 主键 * @return 是否成功 */ @Override public boolean deleteById($!pk.shortType $!pk.name) { return this.$!{tool.firstLowerCase($!{tableInfo.name})}Mapper.deleteById($!pk.name) > 0; } } controller模板 ##定义初始变量 #set($tableName = $tool.append($tableInfo.name, "Controller")) ##设置回调 $!callback.setFileName($tool.append($tableName, ".java")) $!callback.setSavePath($tool.append($tableInfo.savePath, "/controller")) ##拿到主键 #if(!$tableInfo.pkColumn.isEmpty()) #set($pk = $tableInfo.pkColumn.get(0)) #end #if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}controller; import $!{tableInfo.savePackageName}.model.vo.$!{tableInfo.name}Vo; import $!{tableInfo.savePackageName}.model.dto.$!{tableInfo.name}Dto; import $!{tableInfo.savePackageName}.model.mapper.$!{tableInfo.name}ModelMapper; import $!{tableInfo.savePackageName}.service.$!{tableInfo.name}Service; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Range; import org.springframework.validation.annotation.Validated; import org.springframework.beans.BeanUtils; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; /** * $!{tableInfo.comment}($!{tableInfo.name})表控制层 * * @author $!author * @since $!time.currTime() */ @Api(tags = "$!{tableInfo.comment}($!{tableInfo.name})") @Validated @RestController @RequestMapping("$!tool.firstLowerCase($tableInfo.name)") public class $!{tableName} { /** * 服务对象 */ @Resource private $!{tableInfo.name}Service $!tool.firstLowerCase($tableInfo.name)Service; /** * 通过主键查询单条数据 * * @param id 主键 * @return 单条数据 */ @GetMapping("selectOne") public $!{tableInfo.name}Vo selectOne($!pk.shortType id) { $!{tableInfo.name}Dto $!{tool.firstLowerCase($!{tableInfo.name})}Dto = $!{tool.firstLowerCase($!{tableInfo.name})}Service.queryById(id); return $!{tableInfo.name}ModelMapper.INSTANCE.changeDTO2VO($!{tool.firstLowerCase($!{tableInfo.name})}Dto); } } mapperXml模板 ##引入mybatis支持 $!init $!mybatisSupport ##设置保存名称与保存位置 $!callback.setFileName($tool.append($!{tableInfo.name}, "Mapper.xml")) $!callback.setSavePath($tool.append($modulePath, "/src/main/resources/mappers")) ##拿到主键 #if(!$tableInfo.pkColumn.isEmpty()) #set($pk = $tableInfo.pkColumn.get(0)) #end #foreach($column in $tableInfo.fullColumn) #end

5.对数据库表前缀进行截取 在这里插入图片描述

6.使用IDEA连接Mysql数据库,并使用easycode生成代码 在这里插入图片描述

修改代码生成的包路径点击生成即可。 在这里插入图片描述

代码展示

生成的代码如下:

controller类

在这里插入图片描述

serviceImpl类

在这里插入图片描述

dao类

在这里插入图片描述

mapstruct实体转换类

在这里插入图片描述

数据实体类PO

在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有