之前一直把用户上传的图片和文件保存在本地服务器的文件系统中,长而久之会产生以下弊端:
- 当文件数量过多之后严重消耗Linux文件系统的inode;
- 当数据量过大之后不易分布式扩展;
- 数据备份困难,不方便前端展;
- 文件的目录层级越来越深导致文件查找的速度逐渐变慢;
于是想搭建1个私有的阿里云-OSS服务,即对象存储服务;
由专门的对象存储服务来统一管理文件,文件上传之后OSS返回1个唯一的URL地址,后端返回前端,用户可以直接访问;
二、存储分类根据不同的分类方法,存储也会被分成不同的类型,但最终到的用途是都是一致的存放数据;
1.本地存储、外置存储
根据存储设备所在的位置不同划分为
本地存储:电脑内置的存储设备例如硬盘、内存;
外置存储:U盘、移动硬盘
2.DAS、SAN、NAS
根据连接存储设备的介质不同划分为
DAS(Direct Attached Stroage):直连使存储,使用连接介质直接连接到主机、服务器等设备上,连接无需借助网络;
SAN(Stroage Area Network): 存储区域网络,使用专有的网络(非以太网)连接存储设备;
NAS(Network Attached Stroage):网络附加存储,任何1个终端(主机、服务器)都可以通过网络连接到存储设备上;
3.块存储、文件存储、对象存储
根据存储设备文件系统所在位置的不同划分为
块存储: 一切以磁盘形式存在的存储设备都可以称为块存储,块存储使用终端的文件系统,存储服务不利于共享;
文件存储: 块存储拥有了文件系统之后称为文件存储,文件存储使用服务端的文件系统,存储服务利于共享,文件系统使用传统的目录结构管理文件;
对象存储:对象存储的文件系统使用二层结构(Bucket+对象ID)来管理文件,适合存储非结构化数据,速度快、存储服务利于共享、可扩展性强;
三、Minio概述以上我们得知对象存储是1种全新的存储架构,综合了块存储、和文件存储的优点;
对象存储颠覆了传统的文件系统以目录结构管理文件的方式,对象存储的文件系统使用2层结构(Bucket+对象ID)来管理1个唯一的文件对象,适合存储非结构化数据;
对象存储服务读写速度快、易于共享、可扩展性强;
Minio是一款可以实现分布式对象存储服务的软件,我一般会使用minio存储图片、合同、短视频等非结构化数据;
四、搭建Minio服务Minion是使用Golang开发下载完了二进制可执行文件,直接在当操作系统运行服务;
[root@localhost /]# minio server --console-address ":8000" ./data/ WARNING: Detected Linux kernel version older than 4.0.0 release, there are some known potential performance problems with this kernel version. MinIO recommends a minimum of 4.x.x linux kernel version for best performance Automatically configured API requests per node based on available memory on the system: 151 Finished loading IAM sub-system (took 0.0s of 0.0s to load data). Status: 1 Online, 0 Offline. API: http://192.168.56.18:9000 http://192.168.122.1:9000 http://127.0.0.1:9000 RootUser: minioadmin RootPass: minioadmin Console: http://192.168.56.18:8000 http://192.168.122.1:8000 http://127.0.0.1:8000 RootUser: minioadmin RootPass: minioadmin Command-line: https://docs.min.io/docs/minio-client-quickstart-guide $ mc alias set myminio http://192.168.56.18:9000 minioadmin minioadmin Documentation: https://docs.min.io
1.设置权限
访问Minio的Console接口修改对象服务的读写权限;
2.SpringBoot调用minionAPI
1.pom依赖
<!--minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${miniio.version}</version>
</dependency>
2.配置文件
# Miniio配置 minio: endpoint: 192.168.56.18 port: 9000 accessKey: minioadmin secretKey: minioadmin secure: false bucketName: "huike-crm" configDir: "/data/excel"
3.配置类
package com.huike.common.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;
/**
* @className: MinioConfig
* @author Hope
* @date 2022/7/28 13:43
* @description: MinioConfig
*/
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
private final static String HTTP = "http://";
//endPoint是一个URL,域名,IPv4或者IPv6地址
private String endpoint;
//TCP/IP端口号
private int port;
//accessKey类似于用户ID,用于唯一标识你的账户
private String accessKey;
//secretKey是你账户的密码
private String secretKey;
//如果是true,则用的是https而不是http,默认值是true
private Boolean secure;
//默认存储桶
private String bucketName;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
3.上传和下载文件
@Autowired
MinioConfig minioConfig;
@Override
public AjaxResult upload(MultipartFile file) {
InputStream inputStream = null;
//创建Minio的连接对象
MinioClient minioClient = getClient();
String bucketName = minioConfig.getBucketName();
try {
inputStream = file.getInputStream();
//基于官网的内容,判断文件存储的桶是否存在 如果桶不存在就创建桶
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!found) {
// 创建新桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} else {
System.out.println("桶" + bucketName + "已经存在");
}
String filename = file.getOriginalFilename();
String objectName = "/data/excel/" + new SimpleDateFormat("yyyy/MM/dd").format(new Date())
+ "/" + System.currentTimeMillis() + filename.substring(filename.lastIndexOf("."));
//上传文件到minio
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, -1, 10485760)
.contentType("application/pdf")
.build());
System.out.println("上传成功");
Map<String, Object> msgMap = new HashMap<>();
msgMap.put("msg", "操作成功");
msgMap.put("fileName", objectName);
msgMap.put("url", "http://" + minioConfig.getEndpoint() + ":" + minioConfig.getPort() + objectName);
msgMap.put("code", 200);
AjaxResult ajax = AjaxResult.success(msgMap);
/**
* 封装需要的数据进行返回
*/
return ajax;
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("上传失败");
} finally {
//防止内存泄漏
if (inputStream != null) {
try {
inputStream.close(); // 关闭流
} catch (IOException e) {
log.debug("inputStream close IOException:" + e.getMessage());
}
}
}
}
五、EasyExcel
Java解析Excel
1.Java使用EasyExcel
1.1.pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhanggen</groupId>
<artifactId>easy-excel-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
<!--<exclusions>-->
<!--<exclusion>-->
<!--<artifactId>poi-ooxml-schemas</artifactId>-->
<!--<groupId>org.apache.poi</groupId>-->
<!--</exclusion>-->
<!--</exclusions>-->
</dependency>
<!-- lombok 管理 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.2.domain
package com.zhanggen.domain;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode
public class ExcelData {
//@ExcelProperty("字符串标题") 声明当前字段对应的列
@ExcelProperty("字符串标题")
private String title;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double number;
}
---------------------------
package com.zhanggen.domain;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.util.Date;
@Data
public class User {
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private Integer age;
@ExcelProperty("创建时间")
private Date createTime;
@ExcelIgnore//忽略这个字段
private String ignore;
}
1.3.listener监听器
package com.zhanggen.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.zhanggen.domain.ExcelData;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
//准备1个监听器
@Slf4j
// 有个很重要的点ExcelDataReadListener不能被spring管理,就不能从Spring容器中获取dao对象
// 如果想用Spring中的dao对象,需要在当前Listener提供一个构造函数,然后将dao对象接进来
public class ExcelDataReadListener implements ReadListener<ExcelData> {
//控制临时缓存集合的大小(收集到100条之后,往数据库中保存)
private static final int BATCH_COUNT = 100;
//创建缓存集合
private List<ExcelData> cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
// 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
// private DemoDAO demoDAO;
// public ExcelDataReadListener(ExcelData demoDAO) {
// this.demoDAO = demoDAO;
// }
//1.每解析1条Excel记录都会调用的回调函数,把记录保存中临时集合缓存中,一旦临时集合缓存容量达到了上限,就将缓存的数据保存到数据库,然后情况缓存继续收集Excel中的记录
@Override
public void invoke(ExcelData row, AnalysisContext analysisContext) {
System.out.println(row);
cacheDataList.add(row);
//如果达到了临时集合缓存容量的上限,就将缓存的数据保存到数据库
if (cacheDataList.size() >= BATCH_COUNT) {
System.out.println(row);
saveData();
//存储数据库完成之后,清理集合
ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//2.所有数据解析完成了之后调用一次,进行一次存库操作;收尾工作!
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
System.out.println("所有数据解析完成!");
}
//模拟向数据库中保存数据
private void saveData() {
System.out.println(cacheDataList.size() + "条数据,开始存储到数据库!");
//demoDao.save(cacheDataList)
System.out.println("存储数据库完成");
}
}
------------------------
2.4.单元测试
package com.zhanggen.test;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.listener.PageReadListener;
import com.zhanggen.domain.ExcelData;
import com.zhanggen.listener.ExcelDataReadListener;
import org.junit.Test;
import com.alibaba.excel.read.metadata.ReadSheet;
public class ReadExcelTest {
//方式一:
@Test
public void test1() {
String fileName = "D:/upload/excel读取测试.xlsx";
EasyExcel.read(fileName, ExcelData.class, new PageReadListener<ExcelData>(dataList -> {
for (ExcelData row : dataList) {
System.out.println(row);
}
})).sheet().doRead();
}
//方式二:
@Test
public void test2() {
String fileName = "D:/upload/excel读取测试.xlsx";
// 一个文件一个reader
try (ExcelReader excelReader = EasyExcel.read(fileName, ExcelData.class, new ExcelDataReadListener()).build()) {
// 构建一个sheet 这里可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 读取一个sheet
excelReader.read(readSheet);
}
}
}
------------------------
package com.zhanggen.test;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.zhanggen.domain.ExcelData;
import com.zhanggen.domain.User;
import org.junit.Test;
import java.util.Date;
import java.util.List;
public class WriteExcelTest {
private static List<User> list = ListUtils.newArrayList();
//准备输出的数据
static {
for (int i = 0; i < 10; i++) {
User data = new User();
data.setName("字符串" + i);
data.setAge(10 + i);
data.setCreateTime(new Date());
list.add(data);
}
}
@Test
public void test1() {
String fileName = "D:/upload/excel输出测试.xlsx";
EasyExcel.write(fileName, User.class).sheet("模板").doWrite(list);
}
@Test
public void simpleWrite2() {
String fileName = "D:/upload/excel输出测试.xlsx";
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, ExcelData.class).build();
//向1个Excel的Sheet中填充数据
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(list, writeSheet);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
}
2.springBoot使用EasyExcel
1.pom依赖
<!-- easy Excel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${esay_excel.version}</version>
</dependency>
2.监听器
package com.huike.clues.utils.easyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.huike.clues.domain.dto.ImportResultDTO;
import com.huike.clues.domain.vo.TbClueExcelVo;
import com.huike.clues.service.ITbClueService;
/**
* EasyExcel监听器,用于解析数据并执行操作
*/
public class ExcelListener extends AnalysisEventListener<TbClueExcelVo> {
/**
* 利用构造方法获取对应的service
*/
public ITbClueService clueService;
private ImportResultDTO resultDTO;
/**
* 提供带参构造方法,在这里需要通过构造方法的方式获取对应的service层
* 谁调用这个监听器谁提供需要的service
* @param clueService
*/
public ExcelListener(ITbClueService clueService) {
this.clueService = clueService;
this.resultDTO = new ImportResultDTO();
}
/**
* 每解析一行数据都要执行一次
* 每条都执行一次插入操作
* @param row
* @param context
*/
@Override
public void invoke(TbClueExcelVo row, AnalysisContext context) {
ImportResultDTO addTbClue = clueService.importCluesData(row);
resultDTO.addAll(addTbClue);
}
/**
* 当所有数据都解析完成后会执行
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
/**
* 返回结果集对象
* @return
*/
public ImportResultDTO getResult(){
return resultDTO;
}
}
3.Controller
有个很重要的点ExcelDataReadListener不能被Spring容器管理,就不能从Spring容器中获取dao对象;
如果想用Spring中的dao对象,需要在当前Listener提供一个构造函数,然后将dao对象接进来;
import com.alibaba.excel.EasyExcel;
import com.huike.clues.utils.easyExcel.ExcelListener;
@Log(title = "上传线索", businessType = BusinessType.IMPORT) @PostMapping("/importData") public AjaxResult importData(MultipartFile file) throws Exception { ExcelListener excelListener = new ExcelListener(tbClueService); EasyExcel.read(file.getInputStream(), TbClueExcelVo.class, excelListener).sheet().doRead(); return AjaxResult.success(excelListener.getResult()); }
4.Service
@Override
public ImportResultDTO importCluesData(TbClueExcelVo row) {
//创建数据库记录对象
TbClue clue_entry = new TbClue();
//把前端的数据 封装到数据库记录对象属性
BeanUtils.copyProperties(row, clue_entry);
//设置当前线索的创建人
clue_entry.setCreateBy(SecurityUtils.getUsername());
//设置当前线索的创建时间
clue_entry.setCreateTime(DateUtils.getNowDate());
//获取当前活动编号
String activityCode = row.getActivityCode();
//判断活动编号对应的活动是否存在? isNoneBlank如果不为空
if (StringUtils.isNoneBlank(activityCode)) {
TbActivity activity = activityService.selectTbActivityByCode(activityCode);
if (activity == null) {
return ImportResultDTO.error();
} else {
clue_entry.setActivityId(activity.getId());
}
}
//校验当前线索的手机号是否为空?如果为空证明是错误数据,不进行添加 返回error
if (StringUtils.isBlank(clue_entry.getPhone())) {
return ImportResultDTO.error();
}
//判断当前线索的渠道来源是否正确?
if (StringUtils.isNoneBlank(clue_entry.getChannel())) {
String channel = sysDictDataMapper.selectDictValue(TbClue.ImportDictType.CHANNEL.getDictType(), clue_entry.getChannel());
clue_entry.setChannel(channel);
}
//判断当前线索的意向学科是否正确?
if (StringUtils.isNoneBlank(clue_entry.getSubject())) {
String subject = sysDictDataMapper.selectDictValue(TbClue.ImportDictType.SUBJECT.getDictType(), clue_entry.getSubject());
clue_entry.setSubject(subject);
}
//判断当前线索的意向级别是否正确?
if (StringUtils.isNoneBlank(clue_entry.getLevel())) {
String level = sysDictDataMapper.selectDictValue(TbClue.ImportDictType.LEVEL.getDictType(), clue_entry.getLevel());
clue_entry.setLevel(level);//意向级别
}
//判断当前线索的联系人的性别是否正确?
if (StringUtils.isNoneBlank(clue_entry.getSex())) {
String sex = sysDictDataMapper.selectDictValue(TbClue.ImportDictType.SEX.getDictType(),
clue_entry.getSex());//性别
clue_entry.setSex(sex);
}
//数据校验最后1步,设置当前线索的状态为待跟进
clue_entry.setStatus(TbClue.StatusType.UNFOLLOWED.getValue());
//将当前线索写入库
tbClueMapper.insertTbClue(clue_entry);
//根据规则动态分配线索给具体的销售人员,利用策略模式来进行实现
rule.loadRule(clue_entry);
return ImportResultDTO.success();
}
六、Spring项目常见的实体类对象
Java网站后台通常划分为3层即Controller、Service、Dao层,当用户请求到达服务端是,后台就开始进行层层调用,完成客户请求的响应;
为了更好的区别以上不同层使用到的实例对象,我们可以对实体类对象进行以下规则的命名,方便我们区分该对象当前的作用;
1.POJO
全称为:Plain Ordinary Java Object,即简单普通的java对象。
一般用在数据层映射到数据库表的类,类的属性与表字段一一对应。
2.PO
全称为:Persistant Object,即持久化对象。
可以理解为数据库中的一条数据即一个BO对象,也可以理解为POJO经过持久化后的对象。
3.DTO
全称为:Data Transfer Object,即数据传输对象。
一般用于向数据层外围提供仅需的数据,如查询一个表有50个字段,界面或服务只需要用到其中的某些字段,DTO就包装出去的对象。可用于隐藏数据层字段定义,也可以提高系统性能,减少不必要字段的传输损耗。
5.BO
全称为:Business Object,即业务对象。
一般用在业务层,当业务比较复杂,用到比较多的业务对象时,可用BO类组合封装所有的对象一并传递。
6.VO
全称为:Value Object,有的也称为View Object,即值对象或页面对象。一般用于web层向view层封装并提供需要展现的数据。
7.代码示例
以下是1个代码示例,将诠释xxVO对象和XXBO对象的使用场景;
其中商机表和商机表和商机跟进记录表为1对多的关系;
新增商机1条跟进记录,需要更新对应商机记录的的跟进状态;
1.Web层
businessTrackVo 实例对象仅在Web层使用
/**
* 新增商机跟进记录
*/
@PreAuthorize("@ss.hasPermi('business:record:add')")
@Log(title = "商机跟进记录", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody BusinessTrackVo businessTrackVo){
//businessTrackVo 实例对象仅在Controller表示层使用
//1. 创建商机跟进记录对象,并赋值
TbBusinessTrackRecord tbBusinessTrackRecord = new TbBusinessTrackRecord();
//属性拷贝
BeanUtils.copyProperties(businessTrackVo,tbBusinessTrackRecord);
//设置商机跟进记录的创建时间
tbBusinessTrackRecord.setCreateTime(DateUtils.getNowDate());
//设置商机跟进记录的创建人
tbBusinessTrackRecord.setCreateBy(SecurityUtils.getUsername());
//2.创建商机对象并赋值
TbBusiness tbBusiness = new TbBusiness();
//属性拷贝
BeanUtils.copyProperties(businessTrackVo,tbBusiness);
//设置商机的状态
tbBusiness.setStatus(TbBusiness.StatusType.FOLLOWING.getValue());
//设置商机的id
tbBusiness.setId(businessTrackVo.getBusinessId());
//3.调用Service层,传入商机根据记录和商机2个对象便于,更新商机状态、新增商机跟进记录的事务操作;
tbBusinessTrackRecordService.add(tbBusinessTrackRecord,tbBusiness);
return AjaxResult.success();
}
2.Service业务层
从业务层开始,对更新商机记录和新增商机跟进记录这2个业务进行分流;
便于2个子业务(更新商机状态和新增商机跟进记录)成为1组整体的事务操作;
2.1.业务层接口
//新增商机根据记录
void add(TbBusinessTrackRecord businessTrackRecord, TbBusiness tbBusiness);
2.2.业务层实现类
//新增商机根据记录
@Override
@Transactional
public void add(TbBusinessTrackRecord tbBusinessTrackRecord, TbBusiness tbBusiness) {
//业务层更新商机状态
tbBusinessMapper.updateTbBusiness(tbBusiness);
//业务层新增商机跟进记录
tbBusinessTrackRecordMapper.addRecord(tbBusinessTrackRecord);
}
2.3.Mapper持久层
更新商机的Mapper层
public int updateTbBusiness(TbBusiness tbBusiness);
更新商机的MyBatis配置
<update id="updateTbBusiness" parameterType="TbBusiness">
update tb_business
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name = #{name},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="channel != null">channel = #{channel},</if>
<if test="activityId != null">activity_id = #{activityId},</if>
<if test="provinces != null">provinces = #{provinces},</if>
<if test="city != null">city = #{city},</if>
<if test="sex != null and sex != ''">sex = #{sex},</if>
<if test="age != null">age = #{age},</if>
<if test="weixin != null">weixin = #{weixin},</if>
<if test="qq != null">qq = #{qq},</if>
<if test="level != null">level = #{level},</if>
<if test="subject != null">subject = #{subject},</if>
<if test="courseId != null">course_id = #{courseId},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="occupation != null">occupation = #{occupation},</if>
<if test="education != null">education = #{education},</if>
<if test="job != null">job = #{job},</if>
<if test="salary != null">salary = #{salary},</if>
<if test="major != null">major = #{major},</if>
<if test="expectedSalary != null">expected_salary = #{expectedSalary},</if>
<if test="reasons != null">reasons = #{reasons},</if>
<if test="plan != null">plan = #{plan},</if>
<if test="planTime != null">plan_time = #{planTime},</if>
<if test="otherIntention != null">other_intention = #{otherIntention},</if>
<if test="nextTime != null">next_time = #{nextTime},</if>
<if test="status != null">status = #{status},</if>
</trim>
where id = #{id}
</update>
新增商机记录的Mapper层
//新增商机根据记录
void addRecord(TbBusinessTrackRecord tbBusinessTrackRecord);
新增商机记录的MyBatis配置
<!--新增商机根据记录-->
<insert id="addRecord" useGeneratedKeys="true" keyProperty="id">
insert into tb_business_track_record
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="businessId != null and businessId != ''">business_id,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="keyItems != null">key_items,</if>
<if test="record != null">record,</if>
<if test="createTime != null">create_time,</if>
<if test="trackStatus != null">track_status,</if>
<if test="nextTime != null">next_time,</if>
<if test="type != null">type,</if>
<if test="falseReason != null">false_reason,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="businessId != null and businessId != ''">#{businessId},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="keyItems != null">#{keyItems},</if>
<if test="record != null">#{record},</if>
<if test="createTime != null">#{createTime},</if>
<if test="trackStatus != null">#{trackStatus},</if>
<if test="nextTime != null">#{nextTime},</if>
<if test="type != null">#{type},</if>
<if test="falseReason != null">#{falseReason},</if>
</trim>
</insert>
参考