diff --git a/ccic-entity/src/main/java/com/ccic/safeliab/entity/ExCustomerRetention.java b/ccic-entity/src/main/java/com/ccic/safeliab/entity/ExCustomerRetention.java
new file mode 100644
index 0000000..c655317
--- /dev/null
+++ b/ccic-entity/src/main/java/com/ccic/safeliab/entity/ExCustomerRetention.java
@@ -0,0 +1,63 @@
+package com.ccic.safeliab.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 客户留存实体类
+ *
+ * @author edwong
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("ex_customer_retention")
+public class ExCustomerRetention extends BaseEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id", type = IdType.ID_WORKER)
+ @JsonFormat(shape = JsonFormat.Shape.STRING)
+ private Long id;
+
+ /**
+ * 客户ID
+ */
+ @JsonFormat(shape = JsonFormat.Shape.STRING)
+ private Long customerId;
+
+ /**
+ * 说明描述
+ */
+ private String description;
+
+ /**
+ * 上传时间
+ */
+ @DateTimeFormat(
+ pattern = "yyyy-MM-dd HH:mm:ss"
+ )
+ @JsonFormat(
+ pattern = "yyyy-MM-dd HH:mm:ss"
+ )
+ private Date uploadTime;
+
+ /**
+ * 附件ID
+ */
+ @JsonFormat(shape = JsonFormat.Shape.STRING)
+ private Long fileId;
+
+ /**
+ * 附件名称
+ */
+ private String fileName;
+
+}
diff --git a/ccic-entity/src/main/java/com/ccic/safeliab/entity/ExFile.java b/ccic-entity/src/main/java/com/ccic/safeliab/entity/ExFile.java
new file mode 100644
index 0000000..5f9cf3e
--- /dev/null
+++ b/ccic-entity/src/main/java/com/ccic/safeliab/entity/ExFile.java
@@ -0,0 +1,33 @@
+package com.ccic.safeliab.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * 附件实体类
+ *
+ * @author edwong
+ * @since 2025-2-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@TableName("ex_file")
+public class ExFile extends BaseEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ @TableId(value = "id", type = IdType.ID_WORKER)
+ @JsonFormat(shape = JsonFormat.Shape.STRING)
+ private Long id;
+ private String fileName;
+ private String originalName;
+ private String fileSuffix;
+ private String filePath;
+ private String businessType;
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/dao/CustomerRetMapper.java b/ccic-exam/src/main/java/com/ccic/safeliab/dao/CustomerRetMapper.java
new file mode 100644
index 0000000..ccf9462
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/dao/CustomerRetMapper.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ccic.safeliab.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.ccic.safeliab.entity.ExCustomerRetention;
+import com.ccic.safeliab.vo.CustomerRetentionVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * Mapper 接口
+ *
+ * @author edwong
+ */
+@Mapper
+public interface CustomerRetMapper extends BaseMapper {
+
+ List selectCustomerRetPage(IPage page, @Param("customerRet") CustomerRetentionVO customerRetentionVO);
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/dao/FileMapper.java b/ccic-exam/src/main/java/com/ccic/safeliab/dao/FileMapper.java
new file mode 100644
index 0000000..9a1f563
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/dao/FileMapper.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ccic.safeliab.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ccic.safeliab.entity.ExFile;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * Mapper 接口
+ *
+ * @author edwong
+ */
+@Mapper
+public interface FileMapper extends BaseMapper {
+
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/service/CustomerRetService.java b/ccic-exam/src/main/java/com/ccic/safeliab/service/CustomerRetService.java
new file mode 100644
index 0000000..0eed553
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/service/CustomerRetService.java
@@ -0,0 +1,16 @@
+package com.ccic.safeliab.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.ccic.safeliab.entity.ExCustomerRetention;
+import com.ccic.safeliab.support.BaseService;
+import com.ccic.safeliab.vo.CustomerRetentionVO;
+
+/**
+ * 服务类
+ *
+ * @author edwong
+ */
+public interface CustomerRetService extends BaseService {
+
+ IPage findPage(CustomerRetentionVO cusRet, int page, int num);
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/service/CustomerRetServiceImpl.java b/ccic-exam/src/main/java/com/ccic/safeliab/service/CustomerRetServiceImpl.java
new file mode 100644
index 0000000..b0a811d
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/service/CustomerRetServiceImpl.java
@@ -0,0 +1,27 @@
+package com.ccic.safeliab.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ccic.safeliab.dao.CustomerRetMapper;
+import com.ccic.safeliab.entity.ExCustomerRetention;
+import com.ccic.safeliab.support.BaseServiceImpl;
+import com.ccic.safeliab.support.Condition;
+import com.ccic.safeliab.vo.CustomerRetentionVO;
+import org.springframework.stereotype.Service;
+
+/**
+ * 服务实现类
+ *
+ * @author edwong
+ */
+@Service
+public class CustomerRetServiceImpl extends BaseServiceImpl implements CustomerRetService {
+
+ @Override
+ public IPage findPage(CustomerRetentionVO cusRet, int page, int num) {
+ IPage iPage = new Page<>(page, num);
+ iPage.setRecords(baseMapper.selectCustomerRetPage(iPage, cusRet));
+ return iPage;
+ }
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/service/FileService.java b/ccic-exam/src/main/java/com/ccic/safeliab/service/FileService.java
new file mode 100644
index 0000000..d233d45
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/service/FileService.java
@@ -0,0 +1,21 @@
+package com.ccic.safeliab.service;
+
+import com.alibaba.nacos.shaded.com.google.protobuf.ServiceException;
+import com.ccic.safeliab.entity.ExFile;
+import com.ccic.safeliab.support.BaseService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * 服务类
+ *
+ * @author edwong
+ */
+public interface FileService extends BaseService {
+
+ Map saveFileInfo(String fileType, String filePath, String fileName, String fileSuffix, String oldFileName);
+
+ void downloadFile(String fileName, HttpServletResponse response);
+
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/service/FileServiceImpl.java b/ccic-exam/src/main/java/com/ccic/safeliab/service/FileServiceImpl.java
new file mode 100644
index 0000000..3fe13b6
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/service/FileServiceImpl.java
@@ -0,0 +1,94 @@
+package com.ccic.safeliab.service;
+
+import com.alibaba.nacos.shaded.com.google.protobuf.ServiceException;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ccic.safeliab.dao.FileMapper;
+import com.ccic.safeliab.entity.ExFile;
+import com.ccic.safeliab.support.BaseServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 服务实现类
+ *
+ * @author edwong
+ */
+@Slf4j
+@Service
+public class FileServiceImpl extends BaseServiceImpl implements FileService {
+
+ @Override
+ public Map saveFileInfo(String fileType, String filePath, String fileName, String fileSuffix, String oldFileName) {
+ ExFile file = new ExFile();
+ file.setFileName(fileName);
+ file.setOriginalName(oldFileName);
+ file.setFileSuffix(fileSuffix);
+ file.setFilePath(filePath);
+ file.setBusinessType(fileType);
+ boolean saveFlag = super.save(file);
+ Map map = new HashMap<>();
+ if (saveFlag) {
+ map.put("id", file.getId().toString());
+ map.put("url", "./ex/file/download?fileName=" + fileName);
+ map.put("fileName", fileName);
+ map.put("name", oldFileName);
+ return map;
+ }
+ return null;
+ }
+
+ @Override
+ public void downloadFile(String fileName, HttpServletResponse response) {
+ QueryWrapper wrapper = new QueryWrapper<>();
+ wrapper.lambda().eq(ExFile::getFileName, fileName);
+ ExFile info = super.getOne(wrapper, false);
+
+ if (info == null) {
+ log.error("下载文件出错,文件名: {}", fileName);
+ return;
+ }
+
+ String path = "." + info.getFilePath() + info.getFileName() + info.getFileSuffix();
+ String osName = System.getProperties().getProperty("os.name");
+
+ if (osName.contains("Windows")) {
+ path = "C:/" + path;
+ }
+
+ File file = new File(path);
+ if (!file.exists()) {
+ log.error("下载文件出错,文件路径: {}", path);
+ return;
+ }
+
+ try {
+ String realName = info.getOriginalName();
+ // 3. 设置想办法让浏览器能够支持(Content-Disposition)下载我们需要的东西,中文文件名URLEncoder.encode编码,否则有可能乱码
+ response.setHeader("Content-Disposition", "attachment; filename=\"" + new String(realName.getBytes(), "ISO8859-1") + "\"");
+ response.setCharacterEncoding("UTF-8");
+ // 4. 获取下载文件的输入流
+ FileInputStream in = new FileInputStream(path);
+ // 5. 创建缓冲区
+ int len = 0;
+ byte[] buffer = new byte[1024];
+ // 6. 获取OutputStream对象
+ ServletOutputStream out = response.getOutputStream();
+ // 7. 将FileOutputStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端!
+ while ((len = in.read(buffer)) > 0) {
+ out.write(buffer, 0, len);
+ }
+ in.close();
+ out.close();
+ } catch (Exception e) {
+ log.error("下载文件出错,原因: {}", e.getMessage());
+ }
+
+ }
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/service/StatisticsService.java b/ccic-exam/src/main/java/com/ccic/safeliab/service/StatisticsService.java
index 1fc65e8..4c3d413 100644
--- a/ccic-exam/src/main/java/com/ccic/safeliab/service/StatisticsService.java
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/service/StatisticsService.java
@@ -29,5 +29,5 @@ import java.util.Map;
*/
public interface StatisticsService {
- Page findPage(CustomerVO customer);
+ Page findPage(CustomerVO customer, int page, int num);
}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/service/StatisticsServiceImpl.java b/ccic-exam/src/main/java/com/ccic/safeliab/service/StatisticsServiceImpl.java
index 90e45a7..954c5c5 100644
--- a/ccic-exam/src/main/java/com/ccic/safeliab/service/StatisticsServiceImpl.java
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/service/StatisticsServiceImpl.java
@@ -42,8 +42,8 @@ public class StatisticsServiceImpl implements StatisticsService {
private ICustomerService customerService;
@Override
- public Page findPage(CustomerVO customer) {
- Page page = new Page(customer.getPage(), customer.getNum());
+ public Page findPage(CustomerVO customer, int page, int num) {
+ Page iPage = new Page(customer.getPage(), customer.getNum());
QueryWrapper wrapper = Condition.getQueryWrapper(customer);
if (!StringUtils.isEmpty(customer.getKeyword())) {
// 客户名称与编号过滤
@@ -60,8 +60,9 @@ public class StatisticsServiceImpl implements StatisticsService {
wrapper.lambda().eq(Customer::getTypePid, customer.getTypePid());
}
wrapper.orderByDesc("changed_at");
- customerService.page(page,wrapper);
+ wrapper.orderByDesc("customer_id");
+ customerService.page(iPage,wrapper);
- return page;
+ return iPage;
}
}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/util/DateTimeUtils.java b/ccic-exam/src/main/java/com/ccic/safeliab/util/DateTimeUtils.java
new file mode 100644
index 0000000..275d29c
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/util/DateTimeUtils.java
@@ -0,0 +1,440 @@
+package com.ccic.safeliab.util;
+
+
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 日期处理工具类
+ */
+public class DateTimeUtils {
+
+ public static final String FULL_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+ public static final String FULL_DATE_FORMAT_CN = "yyyy年MM月dd日 HH时mm分ss秒";
+ public static final String PART_DATE_FORMAT = "yyyy-MM-dd";
+ public static final String PART_DATE_FORMAT_CN = "yyyy年MM月dd日";
+ public static final String YEAR_DATE_FORMAT = "yyyy";
+ public static final String MONTH_DATE_FORMAT = "MM";
+ public static final String DAY_DATE_FORMAT = "dd";
+ public static final String WEEK_DATE_FORMAT = "week";
+ public static final String YEAR_MONTH_DATE_FORMAT = "yyyyMMdd";
+
+
+ /**
+ * 将日期类型转换为字符串
+ *
+ * @param date 日期
+ * @param xFormat 格式
+ * @return
+ */
+ public static String getFormatDate(Date date, String xFormat) {
+ date = date == null ? new Date() : date;
+ xFormat = StringUtils.isNotEmpty(xFormat) ? xFormat : FULL_DATE_FORMAT;
+ SimpleDateFormat sdf = new SimpleDateFormat(xFormat);
+ return sdf.format(date);
+ }
+
+
+ /**
+ * 比较日期大小
+ *
+ * @param dateX
+ * @param dateY
+ * @return x < y return [-1];
+ * x = y return [0] ;
+ * x > y return [1] ;
+ */
+ public static int compareDate(Date dateX, Date dateY) {
+ return dateX.compareTo(dateY);
+ }
+
+
+ /**
+ * 将日期字符串转换为日期格式类型
+ *
+ * @param xDate
+ * @param xFormat 为NULL则转换如:2012-06-25
+ * @return
+ */
+ public static Date parseString2Date(String xDate, String xFormat) {
+ while (!isNotDate(xDate)) {
+ xFormat = StringUtils.isNotEmpty(xFormat) == true ? xFormat : PART_DATE_FORMAT;
+ SimpleDateFormat sdf = new SimpleDateFormat(xFormat);
+ Date date = null;
+ try {
+ date = sdf.parse(xDate);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return date;
+ }
+ return null;
+ }
+
+
+ /**
+ * 判断需要转换类型的日期字符串是否符合格式要求
+ *
+ * @param xDate
+ * @return
+ */
+ public static boolean isNotDate(String xDate) {
+ SimpleDateFormat sdf = new SimpleDateFormat(PART_DATE_FORMAT);
+ try {
+ if (StringUtils.isEmpty(xDate)) {
+ return true;
+ }
+ sdf.parse(xDate);
+ return false;
+ } catch (ParseException e) {
+ e.printStackTrace();
+ return true;
+ }
+ }
+
+ public static boolean isDate(String xDate) {
+ return !isDate(xDate);
+ }
+
+
+ /**
+ * 获取俩个日期之间相差天数
+ *
+ * @param dateX
+ * @param dateY
+ * @return
+ */
+ public static int getDiffDays(Date dateX, Date dateY) {
+ if ((dateX == null) || (dateY == null)) {
+ return 0;
+ }
+
+ int dayX = (int) (dateX.getTime() / (60 * 60 * 1000 * 24));
+ int dayY = (int) (dateY.getTime() / (60 * 60 * 1000 * 24));
+
+ return dayX > dayY ? dayX - dayY : dayY - dayX;
+ }
+
+ /**
+ * 获取俩个日期之间相差天数(日期)
+ *
+ * @param dateX
+ * @param dateY
+ * @return
+ */
+ public static int getDiffDaysNoABS(Date dateX, Date dateY) {
+ if ((dateX == null) || (dateY == null)) {
+ return 0;
+ }
+
+ int dayX = (int) (dateX.getTime() / (60 * 60 * 1000 * 24));
+ int dayY = (int) (dateY.getTime() / (60 * 60 * 1000 * 24));
+
+ return dayX - dayY;
+ }
+
+
+ /**
+ * 获取传值日期之后几天的日期并转换为字符串类型
+ *
+ * @param date 需要转换的日期 date 可以为NULL 此条件下则获取当前日期
+ * @param after 天数
+ * @param xFormat 转换字符串类型 (可以为NULL)
+ * @return
+ */
+ public static String getAfterCountDate(Date date, int after, String xFormat) {
+ date = date == null ? new Date() : date;
+ xFormat = StringUtils.isNotEmpty(xFormat) == true ? xFormat : PART_DATE_FORMAT;
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.add(Calendar.DAY_OF_MONTH, after);
+ return getFormatDate(calendar.getTime(), xFormat);
+ }
+
+ /**
+ * 获取传值日期之前几天的日期并转换为字符串类型
+ *
+ * @param date 需要转换的日期 date 可以为NULL 此条件下则获取当前日期
+ * @param before 天数
+ * @param xFormat 转换字符串类型 (可以为NULL)
+ * @return
+ */
+ public static String getBeforeCountDate(Date date, int before, String xFormat) {
+ date = date == null ? new Date() : date;
+ xFormat = StringUtils.isNotEmpty(xFormat) == true ? xFormat : PART_DATE_FORMAT;
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.add(Calendar.DAY_OF_MONTH, -before);
+ return getFormatDate(calendar.getTime(), xFormat);
+ }
+
+
+ /**
+ * 获取日期的参数 如:年 , 月 , 日 , 星期几
+ *
+ * @param xDate 日期 可以为日期格式,可以是字符串格式; 为NULL或者其他格式时都判定为当前日期
+ * @param xFormat 年 yyyy 月 MM 日 dd 星期 week ;其他条件下都返回0
+ */
+ public static int getDateTimeParam(Object xDate, String xFormat) {
+ xDate = xDate == null ? new Date() : xDate;
+ Date date = null;
+ if (xDate instanceof String) {
+ date = parseString2Date(xDate.toString(), null);
+ } else if (xDate instanceof Date) {
+ date = (Date) xDate;
+ } else {
+ date = new Date();
+ }
+ date = date == null ? new Date() : date;
+ if (StringUtils.isNotEmpty(xFormat)
+ && (xFormat.equals(YEAR_DATE_FORMAT)
+ || xFormat.equals(MONTH_DATE_FORMAT)
+ || xFormat.equals(DAY_DATE_FORMAT))) {
+ return Integer.parseInt(getFormatDate(date, xFormat));
+ } else if (StringUtils.isNotEmpty(xFormat)
+ && (WEEK_DATE_FORMAT.equals(xFormat))) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(date);
+ int week = cal.get(Calendar.DAY_OF_WEEK) - 1 == 0 ?
+ 7 : cal.get(Calendar.DAY_OF_WEEK) - 1;
+ return week;
+ } else {
+ return 0;
+ }
+ }
+
+
+ /**
+ * 日期格式转换为时间戳
+ *
+ * @param time
+ * @param format
+ * @return
+ */
+ public static Long getLongTime(String time, String format) {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ Date date = null;
+ try {
+ date = sdf.parse(time);
+ return (date.getTime() / 1000);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+ /**
+ * 获取星期字符串
+ *
+ * @param xDate
+ * @return
+ */
+ public static String getWeekString(Object xDate) {
+ int week = getDateTimeParam(xDate, WEEK_DATE_FORMAT);
+ switch (week) {
+ case 1:
+ return "星期一";
+ case 2:
+ return "星期二";
+ case 3:
+ return "星期三";
+ case 4:
+ return "星期四";
+ case 5:
+ return "星期五";
+ case 6:
+ return "星期六";
+ case 7:
+ return "星期日";
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * 获得十位时间
+ */
+ public static Long getTenBitTimestamp() {
+ return System.currentTimeMillis() / 1000;
+ }
+
+ /**
+ * 获得某天的结束时间
+ */
+ public static Date getDateEnd(Date date) {
+ return new Date(date.getTime() + (86400 - 1) * 1000);
+ }
+
+ /**
+ * 日期格式转换为毫秒
+ *
+ * @param time
+ * @param format
+ * @return
+ */
+ public static Long getLongDateTime(String time, String format) {
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ Date date = null;
+ try {
+ date = sdf.parse(time);
+ return date.getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * 获取某天开始时间戳_10位
+ */
+ public static Long getStartTimestamp(Date date) {
+
+ Calendar calendar = Calendar.getInstance();
+ date = date == null ? new Date() : date;
+ calendar.setTime(date);
+
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ return calendar.getTime().getTime() / 1000;
+ }
+
+ /**
+ * 获取某天结束时间戳_10位
+ */
+ public static Long getEndTimestamp(Date date) {
+
+ Calendar calendar = Calendar.getInstance();
+ date = date == null ? new Date() : date;
+ calendar.setTime(date);
+
+ calendar.set(Calendar.HOUR_OF_DAY, 23);
+ calendar.set(Calendar.MINUTE, 59);
+ calendar.set(Calendar.SECOND, 59);
+ calendar.set(Calendar.MILLISECOND, 999);
+
+ return calendar.getTime().getTime() / 1000;
+ }
+
+ /**
+ * 获取昨天日期
+ *
+ * @param date
+ * @return
+ */
+ public static Date getYesterday(Date date) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.add(Calendar.DAY_OF_MONTH, -1);
+
+ calendar.set(Calendar.HOUR_OF_DAY, 9);
+ calendar.set(Calendar.MINUTE, 59);
+ calendar.set(Calendar.SECOND, 59);
+ calendar.set(Calendar.MILLISECOND, 999);
+ date = calendar.getTime();
+ return date;
+ }
+
+ /**
+ * 获取明天时间(参数时间+1天)
+ *
+ * @param date
+ * @return
+ */
+ public static Date getTomorrowday(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.add(Calendar.DAY_OF_YEAR, +1);
+ return c.getTime();
+ }
+
+ /* 10位int型的时间戳转换为String(yyyy-MM-dd HH:mm:ss)
+ *
+ * @param time
+ * @return
+ */
+ public static String timestampToString(Integer time, String format) {
+ // int转long时,先进行转型再进行计算,否则会是计算结束后在转型
+ long temp = (long) time * 1000;
+ Timestamp ts = new Timestamp(temp);
+ String tsStr = "";
+ DateFormat dateFormat = new SimpleDateFormat(format);
+ try {
+ // 方法一
+ tsStr = dateFormat.format(ts);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return tsStr;
+ }
+
+ /**
+ * 获取某天开始时间
+ */
+ public static Date getStartTime(Date date) {
+
+ Calendar calendar = Calendar.getInstance();
+ date = date == null ? new Date() : date;
+ calendar.setTime(date);
+
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ return calendar.getTime();
+ }
+
+ /**
+ * 获取某天结束时间
+ */
+ public static Date getEndTime(Date date) {
+
+ Calendar calendar = Calendar.getInstance();
+ date = date == null ? new Date() : date;
+ calendar.setTime(date);
+
+ calendar.set(Calendar.HOUR_OF_DAY, 23);
+ calendar.set(Calendar.MINUTE, 59);
+ calendar.set(Calendar.SECOND, 59);
+ calendar.set(Calendar.MILLISECOND, 999);
+
+ return calendar.getTime();
+ }
+
+ /**
+ * Date类型转换为10位时间戳
+ *
+ * @param time
+ * @return
+ */
+ public static Integer DateToTimestamp(Date time) {
+ Timestamp ts = new Timestamp(time.getTime());
+
+ return (int) ((ts.getTime()) / 1000);
+ }
+
+ /**
+ * 获取当前时间之前或之后几分钟
+ *
+ * @param minute
+ * @return
+ */
+ public static String getTimeByMinute(int minute, Date time) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(time);
+ calendar.add(Calendar.MINUTE, minute);
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime());
+
+ }
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/vo/CustomerRetentionVO.java b/ccic-exam/src/main/java/com/ccic/safeliab/vo/CustomerRetentionVO.java
new file mode 100644
index 0000000..367be37
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/vo/CustomerRetentionVO.java
@@ -0,0 +1,18 @@
+package com.ccic.safeliab.vo;
+
+import com.ccic.safeliab.entity.ExCustomerRetention;
+import lombok.Data;
+
+/**
+ *
+ * 客户留存表
+ *
+ *
+ * @author edwong
+ */
+@Data
+public class CustomerRetentionVO extends ExCustomerRetention {
+
+ private String fileUid;
+
+}
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/web/FileController.java b/ccic-exam/src/main/java/com/ccic/safeliab/web/FileController.java
new file mode 100644
index 0000000..0214700
--- /dev/null
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/web/FileController.java
@@ -0,0 +1,84 @@
+package com.ccic.safeliab.web;
+import com.ccic.safeliab.service.FileService;
+import com.ccic.safeliab.util.DateTimeUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import com.ccic.safeliab.util.R;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Created by edwong on 2025/2/28.
+ * 文件控制器
+ */
+@RestController
+@RequestMapping("/ex/file")
+@Slf4j
+public class FileController {
+
+ @Value("${file.upload_path}")
+ private String uploadPath;
+
+ @Autowired
+ private FileService fileService;
+
+
+ /**
+ * 附件上载
+ */
+ @PostMapping(value = "/upload")
+ public R upload(@RequestPart("file") MultipartFile picture, @RequestParam("fileType") String fileType) {
+ Map resultMap = new HashMap<>();
+ try {
+ if (null != picture) {
+ String sUniqueNewImageName = UUID.randomUUID().toString();
+ String sOriginalFilename = picture.getOriginalFilename();
+ int suffixIndex = sOriginalFilename.lastIndexOf(".");
+ String suffix = "";
+ if (suffixIndex > 0) {
+ suffix = sOriginalFilename.substring(suffixIndex);
+ }
+ String fileSavePath = uploadPath;
+
+ if (StringUtils.isEmpty(fileType)) {
+ fileType = "default";
+ }
+
+ fileSavePath = fileSavePath + fileType + "/" + DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.YEAR_MONTH_DATE_FORMAT) + "/";
+
+ String filePath = "/data/actual/" + fileType + "/" + DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.YEAR_MONTH_DATE_FORMAT) + "/";
+ if (!new File(fileSavePath).exists()) {
+ new File(fileSavePath).mkdirs();
+ }
+ picture.transferTo(new File(fileSavePath + sUniqueNewImageName + suffix));
+
+ resultMap = fileService.saveFileInfo(fileType, filePath, sUniqueNewImageName, suffix, sOriginalFilename);
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return R.error().data(e.getMessage());
+ }
+ return R.ok().data(resultMap);
+ }
+
+ /**
+ * 附件上载
+ */
+ @GetMapping(value = "/download")
+ @ResponseBody
+ public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) {
+ fileService.downloadFile(fileName, response);
+ }
+
+}
+
+
diff --git a/ccic-exam/src/main/java/com/ccic/safeliab/web/StatisticsController.java b/ccic-exam/src/main/java/com/ccic/safeliab/web/StatisticsController.java
index af5e8a2..b6f8f09 100644
--- a/ccic-exam/src/main/java/com/ccic/safeliab/web/StatisticsController.java
+++ b/ccic-exam/src/main/java/com/ccic/safeliab/web/StatisticsController.java
@@ -1,22 +1,59 @@
package com.ccic.safeliab.web;
+import com.ccic.safeliab.entity.ExCustomerRetention;
+import com.ccic.safeliab.service.CustomerRetService;
import com.ccic.safeliab.service.StatisticsService;
import com.ccic.safeliab.util.R;
+import com.ccic.safeliab.vo.CustomerRetentionVO;
import com.ccic.safeliab.vo.CustomerVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
+import java.util.Date;
+/**
+ * 客户统计控制器
+ *
+ * @author edwong
+ */
@RestController
@RequestMapping("/ex/statistics")
public class StatisticsController {
@Autowired
private StatisticsService statisticsService;
+ @Autowired
+ private CustomerRetService customerRetService;
+ /**
+ * 分页 客户列表(统计动态看板)
+ * @param customer
+ * @return
+ */
@PostMapping("/page")
- public R page(@RequestBody CustomerVO customer) {
- return R.ok().dataPage(statisticsService.findPage(customer));
+ public R page(@RequestBody CustomerVO customer,
+ @RequestParam int page,@RequestParam int num) {
+ return R.ok().dataPage(statisticsService.findPage(customer, page, num));
+ }
+
+ /**
+ * 分页 客户留存
+ * @param customerRetention
+ * @return
+ */
+ @PostMapping("/customerRetPage")
+ public R customerRetPage(@RequestBody CustomerRetentionVO customerRetention,
+ @RequestParam int page,@RequestParam int num) {
+ return R.ok().data(customerRetService.findPage(customerRetention, page, num));
+ }
+
+ /**
+ * 新增或修改 客户留存
+ */
+ @PostMapping("/submitCustomerRet")
+ public R submitCustomerRet(@RequestBody ExCustomerRetention customerRetention) {
+ customerRetention.setUploadTime(new Date());
+ return R.ok().data(customerRetService.save(customerRetention));
}
diff --git a/ccic-exam/src/main/resources/mappers/CustomerRetMapper.xml b/ccic-exam/src/main/resources/mappers/CustomerRetMapper.xml
new file mode 100644
index 0000000..3ff9747
--- /dev/null
+++ b/ccic-exam/src/main/resources/mappers/CustomerRetMapper.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT
+ a.id,
+ a.customer_id,
+ a.description,
+ a.upload_time,
+ a.file_id,
+ a.file_name,
+ b.file_name as file_uid
+ FROM
+ ex_customer_retention a
+ LEFT JOIN ex_file b ON b.ID = A.file_id
+ AND b.is_deleted = 0
+ WHERE
+ a.is_deleted = 0
+
+ and a.customer_id = #{customerRet.customerId}
+
+ order by a.updated_at desc
+
+
+