1、完善项目

2、完善合同
3、完善费用项
4、司机管理对接OCR
5、完善运输计划
6、完善临时额度
This commit is contained in:
2026-08-04 02:49:59 +08:00
387 changed files with 22482 additions and 110 deletions

View File

@@ -0,0 +1,22 @@
<?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>
<parent>
<groupId>org.springblade</groupId>
<artifactId>blade-third-party-api</artifactId>
<version>${revision}</version>
</parent>
<artifactId>blade-wps-api</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springblade</groupId>
<artifactId>blade-core-tool</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,17 @@
package org.springblade.thirdparty.wps.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 第三方api wps配置类
* @author bfhuange
* @date 2024/10/31
*/
@ComponentScan("org.springblade.thirdparty.wps")
@EnableConfigurationProperties(WpsProperties.class)
@Configuration
public class ThirdPartyWpsAutoConfiguration {
}

View File

@@ -0,0 +1,15 @@
package org.springblade.thirdparty.wps.config;
import org.springblade.thirdparty.wps.interceptor.WpsRequestInterceptor;
import org.springframework.context.annotation.Bean;
/**
* @author bfhuange
* @since 2024/12/18
*/
public class WpsFeignClientConfig {
@Bean
public WpsRequestInterceptor requestInterceptor(WpsProperties wpsProperties) {
return new WpsRequestInterceptor(wpsProperties);
}
}

View File

@@ -0,0 +1,26 @@
package org.springblade.thirdparty.wps.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* wps 配置类
* @author bfhuange
* @date 2024/10/31
*/
@Data
@ConfigurationProperties(prefix = "third-party.wps")
public class WpsProperties {
/**
* wps基础路径
*/
private String baseUrl;
/**
* ak
*/
private String accessKey;
/**
* sk
*/
private String secretKey;
}

View File

@@ -0,0 +1,33 @@
package org.springblade.thirdparty.wps.constant;
/**
* wps 常量类
* @author bfhuange
* @date 2024/10/30
*/
public class WpsConstant {
/**
* 签名方法
*/
public static final String SIGN_METHOD = "WPS-4";
/**
* 签名请求头
*/
public static final String AUTHORIZATION_HEADER = "Wps-Docs-Authorization";
/**
* 日期请求头
*/
public static final String DATE_HEADER = "Wps-Docs-Date";
/**
* 成功状态码
*/
public static final Integer SUCCESS_CODE = 200;
/**
* 透传token
*/
public static final int TOKEN_TYPE_YES = 1;
/**
* 简单模式参数
*/
public static final String MODE_PARAM = "&simple";
}

View File

@@ -0,0 +1,17 @@
package org.springblade.thirdparty.wps.exception;
/**
* wps 异常
* @author bfhuange
* @date 2024/10/30
*/
public class WpsException extends RuntimeException {
public WpsException(String message) {
super(message);
}
public WpsException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,28 @@
package org.springblade.thirdparty.wps.exception;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.utils.Func;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author bfhuange
* @since 2025/3/29
*/
@AllArgsConstructor
@Slf4j
@RestControllerAdvice
public class WpsExceptionTranslator {
@ExceptionHandler(WpsException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public R handleError(WpsException e) {
log.error("wps异常", e);
return R.fail(ResultCode.INTERNAL_SERVER_ERROR, (Func.isEmpty(e.getMessage()) ? ResultCode.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage()));
}
}

View File

@@ -0,0 +1,42 @@
package org.springblade.thirdparty.wps.feign;
import org.springblade.thirdparty.wps.config.WpsFeignClientConfig;
import org.springblade.thirdparty.wps.pojo.dto.WpsFilePreviewDTO;
import org.springblade.thirdparty.wps.pojo.vo.WpsFilePreviewVO;
import org.springblade.thirdparty.wps.pojo.vo.WpsResultVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**
* wps接口
* @author bfhuange
* @date 2024/10/30
*/
@FeignClient(name = "WPS", url = "${thirdParty.wps.baseUrl}", configuration = WpsFeignClientConfig.class)
public interface IWpsClient {
/**
* 获取文件预览链接
* @param fileId
* @return
*/
@GetMapping(value = "/open/api/preview/v1/files/{fileId}/link", headers = "Content-Type=application/json")
WpsResultVO<WpsFilePreviewVO> getFilePreviewUrl(@PathVariable("fileId") String fileId,
@RequestParam("preview_mode") String previewMode,
@RequestParam("_w_tokentype") int tokenType,
@SpringQueryMap WpsFilePreviewDTO param);
/**
* 获取文件编辑链接
* @param fileId
* @return
*/
@GetMapping(value = "/open/api/edit/v1/files/{fileId}/link", headers = "Content-Type=application/json")
WpsResultVO<WpsFilePreviewVO> getFileEditUrl(@PathVariable("fileId") String fileId,
@RequestParam("preview_mode") String previewMode,
@RequestParam("_w_tokentype") int tokenType,
@SpringQueryMap WpsFilePreviewDTO param);
}

View File

@@ -0,0 +1,116 @@
package org.springblade.thirdparty.wps.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.CollectionUtil;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.thirdparty.wps.config.WpsProperties;
import org.springblade.thirdparty.wps.constant.WpsConstant;
import org.springblade.thirdparty.wps.exception.WpsException;
import org.springframework.util.StringUtils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* wps请求拦截器
* @author bfhuange
* @date 2024/10/30
*/
@Slf4j
@AllArgsConstructor
public class WpsRequestInterceptor implements RequestInterceptor {
private final WpsProperties wpsProperties;
@Override
public void apply(RequestTemplate template) {
//获取uri路径
String path = template.path();
if(StringUtils.hasText(template.queryLine())) {
path = path + template.queryLine();
}
//获取Content-type“application/json"
Collection<String> contentTypes = template.headers().get("Content-Type");
String contentType = CollectionUtil.isEmpty(contentTypes) ? "" : contentTypes.iterator().next();
//日期格式化
DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = dateFormat.format(new Date());
//open不参与签名做替换处理
if (path.startsWith("/open")) {
path = path.replace("/open", "");
}
String sha256body;
//body为空则为空否则返回sha256(body)
byte[] body = template.body();
if (body != null && body.length > 0) {
sha256body = this.getSHA256StrJava(body);
} else {
sha256body = "";
}
//hmac-sha256(secret_key, Ver + HttpMethod + URI + Content-Type + Wps-Date + sha256(HttpBody))
String signature = null;
try {
signature = DigestUtil.hmacSha256Hex(WpsConstant.SIGN_METHOD + template.method().toUpperCase(Locale.ROOT) + path + contentType + date + sha256body, wpsProperties.getSecretKey());
} catch (Exception e) {
log.error("签名异常", e);
throw new WpsException("请求签名异常");
}
template.header(WpsConstant.DATE_HEADER, date);
template.header(WpsConstant.AUTHORIZATION_HEADER, String.format(WpsConstant.SIGN_METHOD + " %s:%s", wpsProperties.getAccessKey(), signature));
}
/**
* 利用java原生的摘要实现SHA256加密
* @param str 加密后的报文
* @return
*/
public String getSHA256StrJava(byte[] str){
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str);
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
log.error("加密异常", e);
throw new WpsException("加密异常");
}
return encodeStr;
}
/**
* 将byte转为16进制
* @param bytes
* @return
*/
private static String byte2Hex(byte[] bytes){
StringBuilder builder = new StringBuilder();
String temp = null;
for (byte aByte : bytes) {
temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
//1得到一位的进行补0操作
builder.append("0");
}
builder.append(temp);
}
return builder.toString();
}
}

View File

@@ -0,0 +1,58 @@
package org.springblade.thirdparty.wps.pojo.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* wps文件预览参数
* @author bfhuange
* @date 2024/10/30
*/
@Data
public class WpsFilePreviewDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文件格式,可选值:
* w:文字文件
* s:表格文件
* p:演示文件
* f:PDF文件
* x:文件类型图片、压缩包、md、eml、代码、其他传空时默认为该类型 ,必填
*/
private String type;
/**
* 适用版本 >= v6.0.2207.20220729
* 预览模式,可选值:
* high_definition:高清预览
* ordinary:普通预览
* cache:缓存预览
* official:公文极速预览(>=v6.1.2302.20230220不传默认official非必填
*/
//@JsonProperty("preview_mode")
//private String previewMode;
/**
* 高清预览支持控制修订痕迹、评论是否显示等参数。高清预览参数wpsPreview=1111111从左到右依次代表
* 第0位格式修订0不显示1显示
* 第1位插入和删除0不显示1显示
* 第2位评论 0不显示1显示
* 第3位以嵌入模式显示修订
* 第4位以气泡模式显示修订
* 第5位0不显示标记1显示标记
* 第6位0最终状态1原始状态
* 注:(第3、4位组合01及11为批注框方式10是嵌入方式 00是批注框显示修订者),非必填
*/
private String wpsPreview;
/**
* 适用版本>=v7.0.2309a.20230907
* 是否透传token传1时透传
*/
//@JsonProperty("_w_tokentype")
//private int tokenType = 1;
/*
合作方自定义参数,要求以 _w_third_ 作为前缀,回调时会一并返回给开发者。
_w_third_appid 、 _w_third_file_id 字段已被使用,对接方请合理避开。
*/
}

View File

@@ -0,0 +1,114 @@
package org.springblade.thirdparty.wps.pojo.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.util.StringUtils;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;
/**
* wps 文件枚举
* @author bfhuange
* @date 2024/10/30
*/
public class WpsFile {
/**
* 预览模式
*/
@AllArgsConstructor
@Getter
public enum PreviewMode {
/**
* 高清预览
*/
HIGH_DEFINITION("high_definition", "高清预览"),
/**
* 普通预览
*/
ORDINARY("ordinary", "普通预览"),
/**
* 缓存预览
*/
CACHE("cache", "缓存预览"),
/**
* 公文极速预览
*/
OFFICIAL("official", "公文极速预览"),
;
/**
* 编码
*/
private final String code;
/**
* 名称
*/
private final String name;
}
/**
* 类型枚举
*/
@AllArgsConstructor
@Getter
public enum Type {
/**
* 文字文件 高清模式
*/
WORD("w", "文字文件", PreviewMode.HIGH_DEFINITION.getCode(), List.of("doc,dot,wps,wpt,docx,dotx,docm,dotm,rtf,txt,mht,mhtml,htm,html,uot3".split(","))),
/**
* 表格文件 普通模式
*/
EXCEL("s", "表格文件", PreviewMode.ORDINARY.getCode(), List.of("xls,xlt,et,xlsx,xltx,csv,xlsm,xltm,ett".split(","))),
/**
* 演示文件 普通模式
*/
PPT("p", "演示文件", PreviewMode.ORDINARY.getCode(), List.of("ppt,pptx,pptm,ppsx,ppsm,pps,potx,potm,dpt,dps,pot".split(","))),
/**
* PDF文件 普通模式
*/
PDF("f", "PDF文件", PreviewMode.ORDINARY.getCode(), List.of("pdf,ofd".split(","))),
/**
* 文件类型图片、压缩包、md、eml、代码、其他普通模式
*/
OTHER("x", "文件类型图片、压缩包、md、eml、代码、其他", PreviewMode.ORDINARY.getCode(), Collections.emptyList()),
;
/**
* 编码
*/
private final String code;
/**
* 名称
*/
private final String name;
/**
* 预览模式
*/
private final String previewMode;
/**
* 文件扩展名列表
*/
private final List<String> exts;
/**
* 根据文件名获取类型
* @param fileName
* @return
*/
public static Type getByFileName(String fileName) {
if (!StringUtils.hasText(fileName) || !fileName.contains(".")) {
// 文件名为空,或文件名不包含. 返回null
return OTHER;
}
// 获取文件扩展名
String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(Locale.ROOT);
return Stream.of(values())
// 扩展名包含在内的
.filter(type -> type.exts.contains(ext))
.findFirst()
.orElse(OTHER);
}
}
}

View File

@@ -0,0 +1,21 @@
package org.springblade.thirdparty.wps.pojo.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* wps 文件预览链接返回结果
* @author bfhuange
* @date 2024/10/30
*/
@Data
public class WpsFilePreviewVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文件在线预览链接,示例:配置域名+/weboffice/office/{file_type}/{file_id}?_w_appid=11&用户自定义参数 +&wpsPreview=1111111&simple&hidecmb
*/
private String link;
}

View File

@@ -0,0 +1,45 @@
package org.springblade.thirdparty.wps.pojo.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* wps响应结果
* @author bfhuange
* @date 2024/10/30
*/
@Data
public class WpsResultVO<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 请求标识id
*/
@JsonProperty("request_id")
private String requestId;
/**
* 错误码
*/
private Integer code;
/**
* 状态信息
*/
private String msg;
/**
* 请求时间
*/
@JsonProperty("request_time")
private long requestTime;
/**
* 响应时间
*/
@JsonProperty("response_time")
private long responseTime;
/**
* 参数
*/
private T data;
}

View File

@@ -0,0 +1,23 @@
package org.springblade.thirdparty.wps.service;
/**
* @author bfhuange
* @date 2024/10/30
*/
public interface IWpsService {
/**
* 根据文件id获取文件预览链接
* @param fileId
* @param fileName
* @return
*/
String getFilePreviewUrl(String fileId, String fileName);
/**
* 根据文件id获取文件编辑链接
* @param fileId
* @param fileName
* @return
*/
String getWpsFileEditUrl(String fileId, String fileName);
}

View File

@@ -0,0 +1,86 @@
package org.springblade.thirdparty.wps.service.impl;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.thirdparty.wps.constant.WpsConstant;
import org.springblade.thirdparty.wps.exception.WpsException;
import org.springblade.thirdparty.wps.feign.IWpsClient;
import org.springblade.thirdparty.wps.pojo.dto.WpsFilePreviewDTO;
import org.springblade.thirdparty.wps.pojo.enums.WpsFile;
import org.springblade.thirdparty.wps.pojo.vo.WpsFilePreviewVO;
import org.springblade.thirdparty.wps.pojo.vo.WpsResultVO;
import org.springblade.thirdparty.wps.service.IWpsService;
import org.springframework.stereotype.Service;
/**
* @author bfhuange
* @date 2024/10/30
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class WpsServiceImpl implements IWpsService {
private final IWpsClient client;
@Override
public String getFilePreviewUrl(String fileId, String fileName) {
if (StringUtil.isBlank(fileId) || StringUtil.isBlank(fileName)) {
log.info("获取wps预览链接 参数 fileId{} fileName{}", fileId, fileName);
throw new WpsException("参数不能为空");
}
WpsFilePreviewDTO previewDTO = new WpsFilePreviewDTO();
// 根据文件名获取文件格式和预览模式
WpsFile.Type type = WpsFile.Type.getByFileName(fileName);
previewDTO.setType(type.getCode());
try {
// get请求对象参数不支持别名需要提取出来
WpsResultVO<WpsFilePreviewVO> resultVO = client.getFilePreviewUrl(fileId, type.getPreviewMode(), WpsConstant.TOKEN_TYPE_YES, previewDTO);
if (resultVO != null && resultVO.getData() != null && WpsConstant.SUCCESS_CODE.equals(resultVO.getCode())) {
// 成功,返回预览链接
String link = resultVO.getData().getLink();
if (WpsFile.Type.EXCEL.equals(type)) {
// excel去掉simple参数以便查看单元格的公式
return link.replace(WpsConstant.MODE_PARAM, "");
}
return link;
}
log.error("获取wps预览链接异常 {}", JSONUtil.toJsonStr(resultVO));
throw new WpsException("获取wps预览链接异常");
} catch (WpsException e) {
throw e;
} catch (Exception e) {
log.error("获取wps预览链接异常", e);
throw new WpsException("获取wps预览链接异常");
}
}
@Override
public String getWpsFileEditUrl(String fileId, String fileName) {
if (StringUtil.isBlank(fileId) || StringUtil.isBlank(fileName)) {
log.info("获取wps编辑链接 参数 fileId{} fileName{}", fileId, fileName);
throw new WpsException("参数不能为空");
}
WpsFilePreviewDTO previewDTO = new WpsFilePreviewDTO();
// 根据文件名获取文件格式和预览模式
WpsFile.Type type = WpsFile.Type.getByFileName(fileName);
previewDTO.setType(type.getCode());
try {
// get请求对象参数不支持别名需要提取出来
WpsResultVO<WpsFilePreviewVO> resultVO = client.getFileEditUrl(fileId, type.getPreviewMode(), WpsConstant.TOKEN_TYPE_YES, previewDTO);
if (resultVO != null && resultVO.getData() != null && WpsConstant.SUCCESS_CODE.equals(resultVO.getCode())) {
// 成功,返回预览链接
return resultVO.getData().getLink();
}
log.error("获取wps编辑链接异常 {}", JSONUtil.toJsonStr(resultVO));
throw new WpsException("获取wps编辑链接异常:"+JSONUtil.toJsonStr(resultVO));
} catch (WpsException e) {
throw e;
} catch (Exception e) {
log.error("获取wps编辑链接异常", e);
throw new WpsException("获取wps编辑链接异常");
}
}
}

View File

@@ -0,0 +1 @@
org.springblade.thirdparty.wps.config.ThirdPartyWpsAutoConfiguration