后台代码
这个文件是放在ftp服务器上,需要远程下载
@GetMapping("/getBase64/{id}")
@ApiOperation("获取文件base64")
public ModelMap getBase64(HttpServletResponse response, @PathVariable String id){
System.out.println(id);
ModelMap modelMap = new ModelMap();
try {
BidOpenNegotiatingAnswer bidOpenNegotiatingAnswer = bidOpenNegotiatingAnswerService.queryById(id);
BibenetFTPClient client = ftpClient();
client.downloadToFile(response.getOutputStream(),bidOpenNegotiatingAnswer.getOfferFilePath());
// byte[] byteArray = client.downloadToByteArray(bidOpenNegotiatingAnswer.getOfferFilePath());
// String data1 = Base64Utils.encodeToString(byteArray);
// modelMap.put("base64",data1);
} catch (Exception e) {
e.printStackTrace();
}
return modelMap;
}
FTPClient.java
package com.bitbid.middleware.components.ftp;
import com.bitbid.middleware.components.exception.BitbidException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
public class BibenetFTPClient {
private static Logger logger = LoggerFactory.getLogger(BibenetFTPClient.class);
private FTPClientPool pool;
public FTPClientPool getPool() {
return pool;
}
public void setPool(FTPClientPool pool) {
this.pool = pool;
}
// FIXME 暂时
public String getStringFtpPath(String fileFtpPath) {
char _colon = ':';
StringBuilder sb = new StringBuilder();
sb.append("ftp://").append(pool.getUsername()).append(_colon)
.append(pool.getPassword()).append("@").append(pool.getHost())
.append(_colon).append(pool.getPort());
if (fileFtpPath.startsWith("/")) {
sb.append(fileFtpPath);
} else {
sb.append("/").append(fileFtpPath);
}
return sb.toString();
}
public String upload(File srcFile) {
String fileName = srcFile.getName();
String fileExtName = StringUtils.substringAfterLast(fileName, ".");
FileInputStream fis = null;
try {
fis = new FileInputStream(srcFile);
return upload(fis, fileExtName);
} catch (FileNotFoundException e) {
throw new BitbidException(
"upload file to ftp failed. local file does not exists. file path is : "
+ srcFile.getAbsolutePath());
} catch (Exception e) {
logger.error("upload file to ftp failed.", e);
throw new BitbidException(e);
} finally {
IOUtils.closeQuietly(fis);
}
}
public String upload(InputStream fis, String fileExtName) {
FTPClient client = null;
try {
client = (FTPClient) pool.borrowClient();
FileFtpPath fileFtpPath = FileFtpPath.generateOne(fileExtName);
// 先切换到根目录
client.changeWorkingDirectory("/");
chg2SubDirIfNotExistsCreateIt(client, fileFtpPath.getFirstLevelFolder());
chg2SubDirIfNotExistsCreateIt(client, fileFtpPath.getSecondLevelFolder());
chg2SubDirIfNotExistsCreateIt(client, fileFtpPath.getThirdLevelFolder());
// FIXME 能否替换成直接判断所有路径?
boolean storeFile = client.storeFile(fileFtpPath.getRandomFileName(), fis);
if(!storeFile){
logger.error("upload file to ftp failed.");
throw new BitbidException("upload file to ftp failed.");
}
return fileFtpPath.getAbsoluteFtpFilePath();
} catch (Exception e) {
logger.error("upload file to ftp failed.", e);
throw new BitbidException(e);
} finally {
returnFTPClientQuietly(client);
}
}
private void returnFTPClientQuietly(FTPClient client) {
try {
if (null != client) {
pool.returnClient(client);
}
} catch (Exception e) {
logger.info("return ftpclient to pool failed, ignore it.", e);
}
}
public byte[] downloadToByteArray(String ftpFilePath) {
if (logger.isDebugEnabled()) {
logger.debug("begin downloadToByteArray file with ftpFilePath : "
+ ftpFilePath);
}
String ftpFileDir = StringUtils.substringBeforeLast(ftpFilePath, "/");
String ftpFileName = StringUtils.substringAfterLast(ftpFilePath, "/");
if (!ftpFileDir.startsWith("/")) {
ftpFileDir = "/" + ftpFileDir;
}
FTPClient client = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
client = (FTPClient) pool.borrowClient();
client.changeWorkingDirectory(ftpFileDir);
client.retrieveFile(ftpFileName, baos);
if (logger.isDebugEnabled()) {
logger.debug("End downloadToByteArray file with ftpFilePath : "
+ ftpFilePath);
}
return baos.toByteArray();
} catch (Exception e) {
logger.error("download file from ftp failed, file path is : "
+ ftpFilePath, e);
throw new BitbidException(e);
} finally {
IOUtils.closeQuietly(baos);
returnFTPClientQuietly(client);
}
}
private void chg2SubDirIfNotExistsCreateIt(FTPClient client,
String directory) throws IOException {
client.changeWorkingDirectory(directory);
if (client.getReplyCode() == 550) {
client.makeDirectory(directory);
client.changeWorkingDirectory(directory);
}
}
/**
* @author wangcong created it at 16:12 in 2019-1-9
* 直接下载,如果使用ByteArrayOutputStream,文件过大的情况下,Arrays.copyOf()会出现Java heap space问题
*/
public void downloadToFile(OutputStream os, String ftpFilePath) {
if (logger.isDebugEnabled()) {
logger.debug("begin downloadToByteArray file with ftpFilePath : "
+ ftpFilePath);
}
String ftpFileDir = StringUtils.substringBeforeLast(ftpFilePath, "/");
String ftpFileName = StringUtils.substringAfterLast(ftpFilePath, "/");
if (!ftpFileDir.startsWith("/")) {
ftpFileDir = "/" + ftpFileDir;
}
FTPClient client = null;
try {
client = (FTPClient) pool.borrowClient();
client.changeWorkingDirectory(ftpFileDir);
client.retrieveFile(ftpFileName, os);
if (logger.isDebugEnabled()) {
logger.debug("End downloadToByteArray file with ftpFilePath : "
+ ftpFilePath);
}
os.flush();
} catch (Exception e) {
logger.error("download file from ftp failed, file path is : "
+ ftpFilePath, e);
throw new BitbidException(e);
} finally {
IOUtils.closeQuietly(os);
returnFTPClientQuietly(client);
}
}
}
FTPClientPool
package com.bitbid.middleware.components.ftp;
import com.bitbid.middleware.components.exception.BitbidException;
import org.apache.commons.net.SocketClient;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPReply;
import java.io.IOException;
/**
* FTP Client 连接池
*/
public class FTPClientPool extends SocketClientPoolSupport {
public static final int DEFAULT_DATA_TIMEOUT = 120000; // two minutes
public static final String DEFAULT_CONTROL_ENCODING = FTP.DEFAULT_CONTROL_ENCODING;
private String username;
private String password;
private boolean binaryMode = true;
private boolean passiveMode;
private FTPClientConfig config;
private String controlEncoding = DEFAULT_CONTROL_ENCODING;
private int dataTimeout = DEFAULT_DATA_TIMEOUT;
public FTPClientPool(){
}
public FTPClientPool(String host, String port, String username, String password) {
super.setHost(host);
super.setPort(Integer.valueOf(port));
this.username = username;
this.password = password;
}
@Override
public boolean validateObject(Object object) {
FTPClient client = (FTPClient) object;
try {
return client.sendNoOp();
} catch (IOException e) {
throw new RuntimeException("Failed to validate client: " + e, e);
}
}
@Override
public void activateObject(Object object) throws Exception {
FTPClient client = (FTPClient) object;
// client.setReaderThread(true);
}
@Override
public void passivateObject(Object object) throws Exception {
FTPClient client = (FTPClient) object;
// client.setReaderThread(false);
}
public String getUsername() {
return username;
}
/**
* 用户名
*
* @param username
*/
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
/**
* 密码
*
* @param password to set.
*/
public void setPassword(String password) {
this.password = password;
}
public boolean isBinaryMode() {
return binaryMode;
}
/**
* 二进制传输模式
*
* @param binaryMode 默认为true
*/
public void setBinaryMode(boolean binaryMode) {
this.binaryMode = binaryMode;
}
/**
* @return the passiveMode
*/
public boolean isPassiveMode() {
return passiveMode;
}
/**
* 被动模式,默认false
*
* @param passiveMode
*/
public void setPassiveMode(boolean passiveMode) {
this.passiveMode = passiveMode;
}
public FTPClientConfig getConfig() {
return config;
}
public void setConfig(FTPClientConfig config) {
this.config = config;
}
/**
* @return the controlEncoding
*/
public String getControlEncoding() {
return controlEncoding;
}
/**
* FTP控制连接的编码格式. 默认为 <code>ISO-8859-1</code>
*
* @param controlEncoding
*/
public void setControlEncoding(String controlEncoding) {
this.controlEncoding = controlEncoding;
}
/**
* @return the dataTimeout
*/
public int getDataTimeout() {
return dataTimeout;
}
/**
* 指定超时时间. 默认为 <code>120000</code>
*
* @param dataTimeout after which the connection should be closed.
* .
*/
public void setDataTimeout(int dataTimeout) {
this.dataTimeout = dataTimeout;
}
@Override
protected void connect(SocketClient client) throws Exception {
FTPClient ftp = (FTPClient) client;
if (config != null) {
ftp.configure(config);
}
ftp.setControlEncoding(getControlEncoding());
super.connect(ftp);
ftp.setDataTimeout(getDataTimeout());
int code = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(code)) {
ftp.disconnect();
throw new BitbidException(String.valueOf(code));
}
if (!ftp.login(getUsername(), getPassword())) {
ftp.disconnect();
throw new BitbidException(String.valueOf(ftp.getReplyCode()));
}
if (isBinaryMode()) {
ftp.setFileType(FTP.BINARY_FILE_TYPE);
}
if (isPassiveMode()) {
ftp.enterLocalPassiveMode();
}
}
@Override
protected void disconnect(SocketClient client) throws Exception {
FTPClient ftp = (FTPClient) client;
if (ftp.isConnected()) {
ftp.logout();
}
super.disconnect(client);
}
@Override
protected SocketClient createSocketClient() {
return new FTPClient();
}
}
FileFtpPath
package com.bitbid.middleware.components.ftp;
import java.io.Serializable;
import java.util.Calendar;
import java.util.UUID;
public class FileFtpPath implements Serializable {
private static final long serialVersionUID = 7406750362102141180L;
private String firstLevelFolder;
private String secondLevelFolder;
private String thirdLevelFolder;
private String randomFileName;
public String getFirstLevelFolder() {
return firstLevelFolder;
}
public String getSecondLevelFolder() {
return secondLevelFolder;
}
public String getThirdLevelFolder() {
return thirdLevelFolder;
}
public String getRandomFileName() {
return randomFileName;
}
public String getAbsoluteFtpFilePath() {
StringBuilder sb = new StringBuilder();
sb.append(firstLevelFolder).append("/").append(secondLevelFolder)
.append("/").append(thirdLevelFolder).append("/")
.append(randomFileName);
return sb.toString();
}
public String getAbsoluteFtpDirPath() {
StringBuilder sb = new StringBuilder();
sb.append(firstLevelFolder).append("/").append(secondLevelFolder)
.append("/").append(thirdLevelFolder);
return sb.toString();
}
public static FileFtpPath generateOne(String fileExtName) {
FileFtpPath f = new FileFtpPath();
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DATE);
int hour = c.get(Calendar.HOUR_OF_DAY);
StringBuilder sb = new StringBuilder();
f.firstLevelFolder = sb.append(year).append(month).toString();
f.secondLevelFolder = String.valueOf(day);
f.thirdLevelFolder = String.valueOf(hour);
StringBuilder sbFileName = new StringBuilder();
sbFileName.append(UUID.randomUUID().toString());
if (fileExtName.startsWith(".")) {
sbFileName.append(fileExtName);
} else {
sbFileName.append(".").append(fileExtName);
}
f.randomFileName = sbFileName.toString();
return f;
}
}
系统登录的token认证拦截器
package com.bitbid.bidopenconsultations.common.aspect.interceptors;
import com.bitbid.middleware.components.login.LoginFacade;
import com.bitbid.middleware.utils.beanfactory.IocBeanFactory;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
/**
* @author : renfeisheng
* @date : 2018-9-22 11:16
* 功能描述 : 适用于当前系统登录的token认证拦截器
*/
public class ToolsTokenValidationInterceptor implements HandlerInterceptor {
public static final String TOKEN_MARK = "tools-token";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
// Content-Type为application/x-www-form-urlencoded;charset的会发送OPTIONS预处理请求,验证接口是否正常,如果是预处理直接通过
if(request.getMethod().equals("OPTIONS")){
return true;
}
String token = getToken(request);
if(StringUtils.isNotBlank(token) && IocBeanFactory.getBean(LoginFacade.class).validate(token)){
return true;
} else {
//包装token认证失败的请求头,实现跨域
wrapResponseHeader(request, response);
//401状态,认证失败
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
/**
* @author DELL created it at 12:57 in 2018-6-11
* 包装token认证失败的请求头,实现跨域
*/
private void wrapResponseHeader(HttpServletRequest request, HttpServletResponse response){
//起源,来自哪个请求,必须和request同源才能接收到
response.addHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
//跨域策略,前后端都为true的情况下才能收到
response.addHeader("Access-Control-Allow-Credentials", "true");
response.addHeader("Access-Control-Max-Age", "3600");
}
/**
* @author wangcong created it at 3:18 in 2018-4-26
* 查找cookie,首先从request.header中查找,若没有,从cookie中查找
*/
private String getToken(HttpServletRequest request){
if(StringUtils.isNotBlank(request.getHeader(TOKEN_MARK))){
return request.getHeader(TOKEN_MARK);
}
if(ArrayUtils.isNotEmpty(request.getCookies())){
Cookie tokenCookie = Arrays.stream(request.getCookies())
.filter(cookie -> StringUtils.containsIgnoreCase(cookie.getName(), TOKEN_MARK))
.findAny().orElse(null);
if(tokenCookie != null){
return tokenCookie.getValue();
}
}
return null;
}
}
注册拦截器-需要放开链接
package com.bitbid.bidopenconsultations.common.config;
import com.bitbid.bidopenconsultations.common.aspect.interceptors.ToolsSessionInteceptor;
import com.bitbid.bidopenconsultations.common.aspect.interceptors.ToolsTokenValidationInterceptor;
import com.bitbid.bidopenconsultations.common.servlet.PDFServerServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author : renfeisheng
* @date : 2018-9-22 11:11
* 功能描述 : 适用于当前系统的WeB的统一配置类
*/
@Configuration
public class ToolsWebConfig implements WebMvcConfigurer {
/**
* @author wangcong created it at 16:48 in 2018-4-18
* 注册统一时间格式
*/
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
}
/**
* @author wangcong created it at 16:48 in 2018-4-18
* 注册拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册SessionHandlerInterceptor
ToolsSessionInteceptor sessionHandler = new ToolsSessionInteceptor();
registry.addInterceptor(sessionHandler).addPathPatterns("/api/v1/bid-tools/**");
//注册Token认证拦截器
ToolsTokenValidationInterceptor tokenValidationInterceptor = new ToolsTokenValidationInterceptor();
registry.addInterceptor(tokenValidationInterceptor)
.addPathPatterns("/api/v1/bid-tools/**")
.excludePathPatterns("/api/v1/bid-tools/login","/api/v1/bid-tools/loginCA","/api/v1/bid-tools/file/*","/api/v1/bid-tools/pluses/*","/api/v1/bid-tools/tender/tenderSign",
"/api/v1/bid-tools/tender/tenderDownLoad","/api/v1/bid-tools/tender/downloadPriceModel","/api/v1/bid-tools/negotiating-answer/getBase64/*");
}
/**
* @author wangcong created it at 15:08 in 2018-4-20
* 添加CORS配置,可能引起CORS攻击
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "OPTIONS", "PUT")
.maxAge(3600);
}
/**
* @author renfeisheng created it at 14:06 in 2018-4-19
* 注册PDFServerServlet
* 金格pdf签章保存-uri: /PDFServer?doc=xxx
*/
@Bean
public ServletRegistrationBean<PDFServerServlet> getPDFServerServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.setServlet(new PDFServerServlet());
registrationBean.addUrlMappings("/PDFSignatureServer/PDFServer");
return registrationBean;
}
}
vue前端
先安装这个包
npm install --save vue-pdf
页面引入
import pdf from 'vue-pdf'
使用
<el-dialog title="查看文件" :visible.sync="offerFilePathVisible" width="70%" top="10vh">
<el-button-group>
<el-button type="primary" icon="el-icon-arrow-left" @click="prePage">上一页</el-button>
<el-button type="primary" @click="nextPage">下一页<i class="el-icon-arrow-right el-icon--right"></i></el-button>
</el-button-group>
<div style="margintop: 10px; color: #409eff">
{{ pageNum }} / {{ pageTotalNum }}
</div>
<div style="height: 600px;overflow: auto;">
<pdf
:page="pageNum"
:src="url"
@progress="loadedRatio = $event"
@num-pages="pageTotalNum = $event"
></pdf>
</div>
</el-dialog>
data参数:
url: '', // http://192.168.1.104:6999/api/v1/bid-tools/negotiating-answer/getBase64/8cf3f0f8e78e4cee9515baf1bfc1fbb3
pageNum: 1,
pageTotalNum: 1, // 总页数
loadedRatio: 0, // 当前页面的加载进度,范围是0-1 ,等于1的时候代表当前页已经完全加载完成了
function
// 上一页
prePage () {
let page = this.pageNum
page = page > 1 ? page - 1 : this.pageTotalNum
this.pageNum = page
},
// 下一页
nextPage () {
let page = this.pageNum
page = page < this.pageTotalNum ? page + 1 : 1
this.pageNum = page
},
tenderFileBtn (scope) {
// let url = pdfUrl + '?doc=' + scope.row.offerFilePath +
// '&height=' + this.winHeight + '&width=' + this.winWidth
// let url = {offerFilePath: scope.row.offerFilePath}
// consultingSummary.getBase64(scope.row.objectId).then((res) => {
// console.log(res)
// })
console.log(this.url)
this.url = process.env.DOWNLOAD_LINK + 'bid-tools/negotiating-answer/getBase64/' + scope.row.objectId
this.offerFilePathVisible = true
},