目录
搭建FTP服务器
实现效果
前端实现页面如下
图片上传到ftp服务器files根目录下,当前我未上传 (其他文件夹可以忽视,这是我以前传的)
点击上传,并上传到服务器,页面如下
此时咱们再查看FTT文件服务器
文件已经加入进去了
具体实现
流程思想
前端显示的图片是遍历数据库中的存储在ftp服务器上的图片地址
当我上传图片:第一是把图片真上传到ftp服务器上,第二是把图片地址存储到数据库中
注意细节
1、我的域名进行了域名解析,也就是123.123.444.554变成了jinruixiang.top,你们如果没解析需要相应进行修改
2、图片类别传参你们可以去掉,token参数也可去掉。想了解token就去Shiro权限管理的博客,利用有上传好的代码,注释详尽
基于Shiro+Springboot+Mybatis+Vue权限管理系统_江河的笑的博客-CSDN博客
数据库设计
ftp_img
ftp_img_type
前端实现
ImgManage,这是我自定义的一个组件
<template>
<div style="width: 100%">
<div class="img_list">
<div v-for="(item, index) in imgList" :key="index">
<img :src="item.imgUrl" alt="" />
<el-button
class="del"
@click="delImg(item)"
size="mini"
type="primary"
plain
>删除</el-button
>
</div>
</div>
<el-upload
class="upload-demo"
action="api/img/upload"
drag
multiple
:auto-upload="false"
:data="{ typeId: cTypeId }"
ref="upload"
:on-success="handleAvatarSuccess"
:headers="{
token:token
}"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">
只能上传jpg/png文件,且不超过500kb
</div>
</el-upload>
<el-button
style="margin-left: 10px"
size="small"
type="success"
@click="submitUpload"
>上传到服务器</el-button
>
</div>
</template>
<script>
export default {
data() {
return {
imgList: [],
token: this.getToken(),
dialogImageUrl: "",
dialogVisible: false,
disabled: false,
};
},
props:{
cTypeId:{
type:Number,
default:4
}
},
methods: {
showMsg(result) {
if (result.status === 200) {
const data = result.data;
if (data.status === 200) {
this.$message({
showClose: true,
message: data.msg,
type: "success",
duration: "600",
});
} else {
this.$message({
showClose: true,
message: data.msg,
type: "error",
duration: "3000",
});
}
} else {
this.$message({
showClose: true,
message: "操作失败,请联系管理员",
type: "error",
duration: "3000",
});
}
},
getImg() {
this.$API.manage
.getImg(this.cTypeId, this.token)
.then((res) => {
if (res.data.status !== 200) {
this.showMsg(res);
} else {
this.imgList = res.data.list;
}
});
},
submitUpload() {
this.$refs.upload.submit();
},
delImg(item) {
this.$API.manage.delImg(item.imgId, this.token).then((res) => {
this.showMsg(res);
if (res.data.status == 200) {
this.getImg();
}
});
},
handleAvatarSuccess(res, file) {
this.getImg();
},
},
mounted() {
this.getImg();
},
};
</script>
<style lang="less" scoped>
.img_list {
width: 100%;
display: flex;
flex-wrap: wrap;
div {
width: 23%;
height: auto;
margin: 1%;
position: relative;
.del {
position: absolute;
right: -1px;
top: -1px;
}
img {
width: 100%;
height: 100%;
}
}
}
</style>
config/index.js跨域处理,只加了proxyTable
'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
const path = require('path')
module.exports = {
dev: {
changeOrigin: true,
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
// assetsPublicPath: './',
proxyTable: {
'/api': {
target: 'http://127.0.0.1:9000',
pathRewrite: {
'^/api': ''
}
}
},
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
port: 80, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'cheap-module-eval-source-map',
// If you have problems debugging vue-files in devtools,
// set this to false - it *may* help
// https://vue-loader.vuejs.org/en/options.html#cachebusting
cacheBusting: true,
cssSourceMap: true
},
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
// assetsPublicPath: '/',
assetsPublicPath: './',
/**
* Source Maps
*/
productionSourceMap: true,
// https://webpack.js.org/configuration/devtool/#production
devtool: '#source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}
}
axios请求文件
const delImg=(imgId,token)=>{
return axios.delete("api/manage/deleteImg",{
params:{
imgId:imgId
},
headers: {
token: token
}
})
}
ImgManage组件调用,我这里传入的是图片类别参数,你们可以不设置,但是相应传参可以去掉
<template>
<div>
<img-manage :cTypeId="$constant.IMG_TYPE.IMG_JIN_GUN"/>
</div>
</template>
<script>
import ImgManage from '@/components/imgManage/ImgManage'
export default {
components: { ImgManage },
}
</script>
<style>
</style>
后端实现
自己建一个utils包,放入ftpUtil.java
package com.carve.carvesystem.utils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
public class FtpUtil {
// ftp服务器ip地址
private static final String FTP_ADDRESS = "xxxx.xxxx.xxx.xxx";
private static final int FTP_PORT = 21;
private static final String FTP_USERNAME = "user-file";
private static final String FTP_PASSWORD = "123";
// 路径
private static final String FTP_BASEPATH = "/home/user-file/files";
// 存储路径
private static final String SAVE_BASEPATH = "http://jinruixiang.top/files/";
public static String uploadFile(MultipartFile file) throws IOException {
//获取上传的文件流
InputStream inputStream = file.getInputStream();
// 获取上传的文件名
String fileName = file.getOriginalFilename();
// 截取后缀
String suffix = fileName.substring(fileName.lastIndexOf("."));
// 使用UUID拼接后缀,定义一个不重复的文件名
String finalName = UUID.randomUUID() + suffix;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(FTP_ADDRESS, FTP_PORT);
ftp.login(FTP_USERNAME, FTP_PASSWORD);
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return null;
}
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
ftp.makeDirectory(FTP_BASEPATH);
ftp.changeWorkingDirectory(FTP_BASEPATH);
ftp.enterLocalPassiveMode();
ftp.storeFile(finalName, inputStream);
inputStream.close();
ftp.logout();
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return SAVE_BASEPATH + finalName;
}
}
controller层的FileController调用
package com.carve.carvesystem.controller;
import com.carve.carvesystem.service.ImgService;
import com.carve.carvesystem.utils.FtpUtil;
import io.swagger.annotations.Api;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
@Api(value = "文件上传,下载相关功能")
@RestController
@RequestMapping("/img")
public class FileController {
@Autowired
private ImgService imgService;
// 文件上传 (可以多文件上传)
@RequiresPermissions({"save"})
@PostMapping("upload")
public String fileUploads(@RequestHeader("token") String token,Integer typeId, @RequestParam("file") MultipartFile file) throws IOException {
// 调用自定义的FTP工具类上传文件
String fileUrl= FtpUtil.uploadFile(file);
if(!StringUtils.isEmpty(fileUrl)){
imgService.addImg(typeId,fileUrl);
// 设置对象中的图片名
// user.setPicPath(fileName)
// 添加用户到数据库
// userService.addUser(user);
}
return fileUrl;
}
}