24282 пре 8 месеци
родитељ
комит
88ee10866e

+ 17 - 4
jy-business/src/main/java/com/jy/business/config/RabbitConfig.java

@@ -12,22 +12,35 @@ import org.springframework.context.annotation.Configuration;
 @Configuration
 public class RabbitConfig {
 
-    public static final String JY_DIRECT_EXCHANGE = "jyDirectExchange";
+    /**
+     * 添加资金流水队列
+     */
     public static final String TRANSACTIONS_QUEUE = "transactionsQueue";
 
-    //创建一个队列
+    /**
+     * jy项目交换机
+     */
+    public static final String JY_DIRECT_EXCHANGE = "jyDirectExchange";
+
+    /**
+     * 创建一个队列
+     */
     @Bean
     public Queue transactionsQueue() {
         return new Queue(TRANSACTIONS_QUEUE, true);
     }
 
-    //创建一个Direct类型的交换机
+    /**
+     * 创建一个Direct类型的交换机
+     */
     @Bean
     public DirectExchange jyDirectExchange() {
         return new DirectExchange(JY_DIRECT_EXCHANGE, true, false);
     }
 
-    //绑定交换机和队列
+    /**
+     * 绑定交换机和队列
+     */
     @Bean
     public Binding binding() {
         return BindingBuilder.bind(transactionsQueue()).to(jyDirectExchange()).withQueueName();

+ 18 - 0
jy-business/src/main/java/com/jy/business/payment/controller/PaymentRequestsController.java

@@ -8,13 +8,17 @@ import com.jy.business.payment.model.vo.PaymentRequestsVo;
 import com.jy.business.payment.service.PaymentRequestsService;
 import com.jy.flow.model.dto.RetrieveDto;
 import com.jy.framework.model.base.BaseSelectDto;
+import com.jy.framework.utils.excel.ExcelUtil;
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 /**
  * <p>
  * 请款 前端控制器
@@ -30,6 +34,9 @@ public class PaymentRequestsController {
     @Resource
     private PaymentRequestsService paymentRequestsService;
 
+    @Resource
+    private HttpServletResponse response;
+
     /**
      * 请款分页
      */
@@ -39,6 +46,17 @@ public class PaymentRequestsController {
     }
 
     /**
+     * 导出
+     */
+    @GetMapping("/excelExport")
+    public void excelExport(PaymentRequestsSelectDto dto) {
+        dto.setSearchAll(true);
+        Page<PaymentRequestsVo> page = getPage(dto);
+        List<PaymentRequestsVo> records = page.getRecords();
+        ExcelUtil.exportExcel(records, "请款信息", PaymentRequestsVo.class, response);
+    }
+
+    /**
      * 请款明细
      */
     @GetMapping("/getDetail")

+ 1 - 1
jy-business/src/main/java/com/jy/business/payment/service/impl/PaymentRequestsServiceImpl.java

@@ -144,7 +144,7 @@ public class PaymentRequestsServiceImpl implements PaymentRequestsService {
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void retrieve(RetrieveDto dto) {
-        paymentRequestsDao.updateFlowStatus(dto.getBusinessId() + "", FlowStatusEnum.RETRIEVE.getKeyStr());
+        paymentRequestsDao.updateFlowStatus(dto.getBusinessId(), FlowStatusEnum.RETRIEVE.getKeyStr());
         executeService.retrieveByBusinessId(dto.getBusinessId());
     }
 

+ 1 - 1
jy-flow/src/main/java/com/jy/flow/model/dto/RetrieveDto.java

@@ -10,6 +10,6 @@ public class RetrieveDto {
     /**
      * 业务id
      */
-    private Long businessId;
+    private String businessId;
 
 }

+ 5 - 0
jy-flow/src/main/java/com/jy/flow/service/ExecuteService.java

@@ -33,4 +33,9 @@ public interface ExecuteService {
      */
     void retrieveByBusinessId(Long businessId);
 
+    /**
+     * 撤回
+     */
+    void retrieveByBusinessId(String businessId);
+
 }

+ 6 - 3
jy-flow/src/main/java/com/jy/flow/service/impl/ExecuteServiceImpl.java

@@ -57,10 +57,13 @@ public class ExecuteServiceImpl implements ExecuteService {
 
     @Override
     public void retrieveByBusinessId(Long businessId) {
-        String businessIdStr = businessId + "";
+        retrieveByBusinessId(businessId + "");
+    }
 
-        Instance instance = insService.getOne(new FlowInstance().setBusinessId(businessIdStr));
-        if (instance == null || ObjectUtil.notEqual(instance.getBusinessId(), businessIdStr)) {
+    @Override
+    public void retrieveByBusinessId(String businessId) {
+        Instance instance = insService.getOne(new FlowInstance().setBusinessId(businessId));
+        if (instance == null || ObjectUtil.notEqual(instance.getBusinessId(), businessId)) {
             throw new ServiceException("未知流程实例");
         }
 

+ 6 - 0
jy-framework/pom.xml

@@ -97,6 +97,12 @@
             <artifactId>hutool-http</artifactId>
         </dependency>
 
+        <!-- easy excel -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.dromara.warm</groupId>
             <artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>

+ 24 - 0
jy-framework/src/main/java/com/jy/framework/utils/ValidatorUtils.java

@@ -0,0 +1,24 @@
+package com.jy.framework.utils;
+
+import cn.hutool.extra.spring.SpringUtil;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validator;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.Set;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ValidatorUtils {
+
+    private static final Validator VALID = SpringUtil.getBean(Validator.class);
+
+    public static <T> void validate(T object, Class<?>... groups) {
+        Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
+        if (!validate.isEmpty()) {
+            throw new ConstraintViolationException("参数校验异常", validate);
+        }
+    }
+
+}

+ 46 - 0
jy-framework/src/main/java/com/jy/framework/utils/excel/ExcelBigNumberConvert.java

@@ -0,0 +1,46 @@
+package com.jy.framework.utils.excel;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+@Slf4j
+public class ExcelBigNumberConvert implements Converter<Long> {
+
+    @Override
+    public Class<Long> supportJavaTypeKey() {
+        return Long.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        return Convert.toLong(cellData.getData());
+    }
+
+    @Override
+    public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNotNull(object)) {
+            String str = Convert.toStr(object);
+            if (str.length() > 15) {
+                return new WriteCellData<>(str);
+            }
+        }
+        WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
+        cellData.setType(CellDataTypeEnum.NUMBER);
+        return cellData;
+    }
+
+}

+ 14 - 0
jy-framework/src/main/java/com/jy/framework/utils/excel/ExcelListener.java

@@ -0,0 +1,14 @@
+package com.jy.framework.utils.excel;
+
+import com.alibaba.excel.read.listener.ReadListener;
+
+/**
+ * Excel 导入监听
+ *
+ * @author Lion Li
+ */
+public interface ExcelListener<T> extends ReadListener<T> {
+
+    ExcelResult<T> getExcelResult();
+
+}

+ 26 - 0
jy-framework/src/main/java/com/jy/framework/utils/excel/ExcelResult.java

@@ -0,0 +1,26 @@
+package com.jy.framework.utils.excel;
+
+import java.util.List;
+
+/**
+ * excel返回对象
+ *
+ * @author Lion Li
+ */
+public interface ExcelResult<T> {
+
+    /**
+     * 对象列表
+     */
+    List<T> getList();
+
+    /**
+     * 错误列表
+     */
+    List<String> getErrorList();
+
+    /**
+     * 导入回执
+     */
+    String getAnalysis();
+}

+ 118 - 0
jy-framework/src/main/java/com/jy/framework/utils/excel/ExcelUtil.java

@@ -0,0 +1,118 @@
+package com.jy.framework.utils.excel;
+
+import cn.hutool.core.util.IdUtil;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * Excel相关处理
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ExcelUtil {
+
+    /**
+     * 同步导入(适用于小数据量)
+     *
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
+        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
+    }
+
+    /**
+     * 使用自定义监听器 异步导入 自定义返回
+     *
+     * @param is       输入流
+     * @param clazz    对象类型
+     * @param listener 自定义监听器
+     * @return 转换后集合
+     */
+    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
+        EasyExcel.read(is, clazz, listener).sheet().doRead();
+        return listener.getExcelResult();
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param list      导出数据集合
+     * @param sheetName 工作表的名称
+     * @param clazz     实体类
+     * @param response  响应体
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
+        try {
+            resetResponse(sheetName, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportExcel(list, sheetName, clazz, os);
+        } catch (IOException e) {
+            throw new RuntimeException("导出Excel异常");
+        }
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param list      导出数据集合
+     * @param sheetName 工作表的名称
+     * @param clazz     实体类
+     * @param os        输出流
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
+        EasyExcel.write(os, clazz)
+                .autoCloseStream(false)
+                // 自动适配
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                // 大数值自动转换 防止失真
+                .registerConverter(new ExcelBigNumberConvert())
+                .sheet(sheetName)
+                .doWrite(list);
+    }
+
+    /**
+     * 重置响应体
+     */
+    private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
+        String filename = encodingFilename(sheetName);
+        String percentEncodedFileName = percentEncode(filename);
+        String contentDispositionValue = "attachment; filename=%s;filename*=utf-8''%s".formatted(percentEncodedFileName, percentEncodedFileName);
+        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
+        response.setHeader("Content-disposition", contentDispositionValue);
+        response.setHeader("download-filename", percentEncodedFileName);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
+    }
+
+    /**
+     * 百分号编码工具方法
+     *
+     * @param s 需要百分号编码的字符串
+     * @return 百分号编码后的字符串
+     */
+    public static String percentEncode(String s) {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8);
+        return encode.replaceAll("\\+", "%20");
+    }
+
+    /**
+     * 编码文件名
+     */
+    public static String encodingFilename(String filename) {
+        return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
+    }
+
+}

+ 6 - 0
jy-ui/src/api/business/payment/requests.ts

@@ -1,4 +1,5 @@
 import request from '@/utils/request'
+import download from '@/utils/download'
 import { PageType, StrAnyObj } from '@/typings'
 
 // 请款分页
@@ -40,4 +41,9 @@ export function remitApi(data: StrAnyObj): Promise<PageType<StrAnyObj>> {
 // 取回流程
 export function retrieveApi(data: StrAnyObj): Promise<void> {
   return request.post(`/paymentRequests/retrieve`,data)
+}
+
+// 下载excel
+export function excelExportApi(params: StrAnyObj): Promise<void> {
+  return download.get(`/paymentRequests/excelExport`,params)
 }

+ 145 - 0
jy-ui/src/utils/download.ts

@@ -0,0 +1,145 @@
+import qs from 'qs'
+import router from '@/router'
+import axios, {AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig} from 'axios'
+import {ElMessage} from 'element-plus'
+import {showFullScreenLoading, tryHideFullScreenLoading} from '@/utils/fullScreen'
+import {getToken} from '@/utils/index'
+
+interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
+  loading?: boolean
+  cancel?: boolean
+}
+
+const LOGIN_URL = import.meta.env.VITE_LOGIN_URL
+
+const config = {
+  // 默认地址请求地址,可在 .env.** 文件中修改
+  baseURL: import.meta.env.VITE_APP_BASE_API,
+  // 设置超时时间
+  timeout: 30000,
+  // 跨域时候允许携带凭证
+  withCredentials: true
+}
+
+// 声明一个 Map 用于存储每个请求的标识 和 取消函数
+const pendingMap = new Map<string, AbortController>()
+
+class RequestHttp {
+  service: AxiosInstance
+
+  public constructor(config: AxiosRequestConfig) {
+    this.service = axios.create(config)
+
+    // 请求拦截器
+    this.service.interceptors.request.use(
+      (config: CustomAxiosRequestConfig) => {
+        // 重复请求不需要取消,在 api 服务中通过指定的第三个参数: { cancel: false } 来控制
+        config.cancel ??= false
+        config.cancel && addPending(config)
+        // 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
+        config.loading ??= true
+        config.loading && showFullScreenLoading()
+        config.headers.set('Authorization', getToken())
+        return config
+      },
+      (error: AxiosError) => {
+        return Promise.reject(error)
+      }
+    )
+
+    // 响应拦截器
+    this.service.interceptors.response.use(
+      (response: AxiosResponse & { config: CustomAxiosRequestConfig }) => {
+        const { data, config } = response
+        removePending(config)
+        config.loading && tryHideFullScreenLoading()
+
+        const blob = new Blob([data], { type: "application/ms-excel" });
+        const url = window.URL.createObjectURL(blob);
+        const a = document.createElement('a');
+        a.style.display = 'none';
+        a.href = url;
+        a.download = decodeURIComponent(response.headers['download-filename']) || '文件.xlsx';
+        document.body.appendChild(a);
+        a.click();
+        window.URL.revokeObjectURL(url);
+        return data
+      },
+      (error: AxiosError) => {
+        const { response } = error
+        tryHideFullScreenLoading()
+        // 请求超时 && 网络错误单独判断,没有 response
+        if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时,请您稍后重试')
+        if (error.message.indexOf('Network Error') !== -1) ElMessage.error('网络错误,请您稍后重试')
+        // 根据服务器响应的错误状态码,做不同的处理
+        if (response) {
+          switch (response.status) {
+            case 400:
+              ElMessage.error('请求失败')
+              break
+            case 404:
+              ElMessage.error('你所访问的资源不存在')
+              break
+            case 500:
+              ElMessage.error('服务异常')
+              break
+            default:
+              ElMessage.error('请求失败')
+          }
+        }
+        // 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
+        if (!window.navigator.onLine) router.replace('/500').then()
+        return Promise.reject(error)
+      }
+    )
+  }
+
+  get<T>(url: string, params?: object, _object = {}): Promise<T> {
+    return this.service.get(url, { params,..._object, responseType: "blob" })
+  }
+
+  post<T>(url: string, data?: object | string, _object = {}): Promise<T> {
+    return this.service.post(url, data, {..._object, responseType: "blob"})
+  }
+
+  put<T>(url: string, data?: object | string, _object = {}): Promise<T> {
+    return this.service.put(url, data, {..._object, responseType: "blob"})
+  }
+
+  delete<T>(url: string, params?: object, _object = {}): Promise<T> {
+    return this.service.delete(url, { params, ..._object, responseType: "blob" })
+  }
+}
+
+// 序列化参数
+function getPendingUrl(config: CustomAxiosRequestConfig) {
+  return [config.method, config.url, sortString(config.data), sortString(config.params)].join('&')
+}
+
+// 序列化参数,确保对象属性顺序一致
+function sortString(obj: any) {
+  return qs.stringify(obj, { arrayFormat: 'repeat', sort: (a, b) => a.localeCompare(b) })
+}
+
+// 添加请求
+function addPending(config: CustomAxiosRequestConfig): void {
+  // 在请求开始前,对之前的请求做检查取消操作
+  removePending(config)
+  const url = getPendingUrl(config)
+  const controller = new AbortController()
+  config.signal = controller.signal
+  pendingMap.set(url, controller)
+}
+
+// 移除请求
+function removePending(config: CustomAxiosRequestConfig): void {
+  const url = getPendingUrl(config)
+  // 如果在 pending 中存在当前请求标识,需要取消当前请求并删除条目
+  const controller = pendingMap.get(url)
+  if (controller) {
+    controller.abort()
+    pendingMap.delete(url)
+  }
+}
+
+export default new RequestHttp(config)

+ 9 - 1
jy-ui/src/views/business/payment/requests/index.vue

@@ -8,7 +8,7 @@ import { useHandleData } from '@/utils/useHandleData'
 import {
   addApi,
   deleteApi,
-  editApi,
+  editApi, excelExportApi,
   getDetailApi,
   getPageApi,
   retrieveApi
@@ -146,6 +146,14 @@ const toolbarConfig: ToolbarConfigType[] = [
       dialogTitle.value = '新增'
       nextTick(() => deptIdRef.value?.load())
     }
+  },
+  {
+    text: '导出',
+    icon: 'Download',
+    type: 'primary',
+    click() {
+      excelExportApi(queryData.value)
+    }
   }
 ]
 

+ 7 - 0
pom.xml

@@ -29,6 +29,7 @@
         <beetl.version>3.17.0.RELEASE</beetl.version>
         <warm-flow.version>1.3.7</warm-flow.version>
         <esdk-obs-java.version>3.24.9</esdk-obs-java.version>
+        <easyexcel.version>4.0.3</easyexcel.version>
     </properties>
 
     <modules>
@@ -132,6 +133,12 @@
             </dependency>
 
             <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>easyexcel</artifactId>
+                <version>${easyexcel.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>com.jy</groupId>
                 <artifactId>jy-starter</artifactId>
                 <version>${jy.version}</version>