Parcourir la source

update:图床功能优化v1

lvzhiqiang il y a 1 an
Parent
commit
739324c2d0

+ 35 - 3
src/main/java/top/lvzhiqiang/controller/CoinController.java

@@ -3,9 +3,12 @@ package top.lvzhiqiang.controller;
 import com.alibaba.fastjson.JSONObject;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
+import top.lvzhiqiang.config.InitRunner;
 import top.lvzhiqiang.dto.R;
 import top.lvzhiqiang.entity.CoinApiConfig;
 import top.lvzhiqiang.entity.FileImage;
+import top.lvzhiqiang.enumeration.ResultCodeEnum;
+import top.lvzhiqiang.exception.BusinessException;
 import top.lvzhiqiang.exception.ParameterException;
 import top.lvzhiqiang.mapper.CoinMapper;
 import top.lvzhiqiang.service.CoinApiConfigService;
@@ -118,7 +121,7 @@ public class CoinController {
      */
     @RequestMapping("/uploadImgs")
     @ResponseBody
-    public R uploadImgs(@RequestParam("file") MultipartFile file, String remark) {
+    public R uploadImgs(@RequestParam("file") MultipartFile file, String remark, Long categoryId) {
         if (file == null || file.getSize() == 0) {
             throw new ParameterException("文件为空!");
         }
@@ -140,7 +143,7 @@ public class CoinController {
             boolean result = FtpUtil.uploadFile(newName, input);
             if (result) {
                 //返回给前端图片访问路径
-                imageUrl = FtpUtil.getBaseUrl() + LocalDate.now().format(DateUtils.dateFormatter_) + "/" + newName;
+                imageUrl = LocalDate.now().format(DateUtils.dateFormatter_) + "/" + newName;
                 imageSize = BigDecimal.valueOf(file.getSize()).divide(new BigDecimal("1024")).setScale(0, RoundingMode.UP).toPlainString().concat("KB");
 
                 FileImage fileImage = new FileImage();
@@ -149,6 +152,7 @@ public class CoinController {
                 fileImage.setSize(imageSize);
                 fileImage.setPath(imageUrl);
                 fileImage.setRemark(remark);
+                fileImage.setCategoryId(categoryId);
                 coinMapper.insertFileImage(fileImage);
             }
         } catch (IOException e) {
@@ -156,9 +160,37 @@ public class CoinController {
         }
 
         JSONObject result = new JSONObject();
-        result.put("imageUrl", imageUrl);
+        result.put("imageUrl", FtpUtil.getBaseUrl() + imageUrl);
         result.put("imageSize", imageSize);
 
         return R.ok().data(result);
     }
+
+    @RequestMapping("/deleteImgs/{imageId}")
+    @ResponseBody
+    public R deleteImgs(@PathVariable Long imageId) {
+        if (imageId == null) {
+            throw new ParameterException("imageId为空!");
+        }
+
+        FileImage fileImage = coinMapper.findFileImageById(imageId);
+        if (fileImage == null) {
+            throw new BusinessException(ResultCodeEnum.UNKNOWN_ERROR.getCode(), "ID 不存在!");
+        }
+
+        try {
+            String ftpBasePath = InitRunner.dicCodeMap.get("ftp_basepath").getCodeValue();
+            boolean flag = FtpUtil.delFile(ftpBasePath + fileImage.getPath());
+
+            if (flag) {
+                coinMapper.deleteFileImageById(imageId);
+                return R.ok();
+            } else {
+                return R.error().message("删除失败");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return R.error().message(e.getMessage());
+        }
+    }
 }

+ 2 - 0
src/main/java/top/lvzhiqiang/entity/CoinApiConfig.java

@@ -1,5 +1,6 @@
 package top.lvzhiqiang.entity;
 
+import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
@@ -79,4 +80,5 @@ public class CoinApiConfig implements Serializable {
     private Integer deleteFlag;
 
     private List<String> trackCategoryList;
+    private List<JSONObject> otherAttrList;
 }

+ 3 - 0
src/main/java/top/lvzhiqiang/entity/FileImage.java

@@ -61,4 +61,7 @@ public class FileImage implements Serializable {
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime modifyTime;
+
+    private String categoryName;
+    private Long categoryId;
 }

+ 4 - 0
src/main/java/top/lvzhiqiang/mapper/CoinApiConfigMapper.java

@@ -1,5 +1,6 @@
 package top.lvzhiqiang.mapper;
 
+import com.alibaba.fastjson.JSONObject;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Insert;
 import org.apache.ibatis.annotations.Select;
@@ -76,4 +77,7 @@ public interface CoinApiConfigMapper {
 
     @Select("select style_name from coin_color_style where delete_flag = 1")
     List<String> findColorStyleList();
+
+    @Select("select id,category_name categoryName from file_image_category where delete_flag = 1")
+    List<JSONObject> findFileImageCategoryList();
 }

+ 13 - 4
src/main/java/top/lvzhiqiang/mapper/CoinMapper.java

@@ -110,19 +110,28 @@ public interface CoinMapper {
     CoinWatchlist findWatchlistBySymbol(String symbol);
 
 
-    @Insert("INSERT INTO file_image(old_name, new_name, size, path, remark, create_time, modify_time) " +
-            "VALUES (#{oldName}, #{newName}, #{size}, #{path}, #{remark}, now(), now())")
+    @Insert("INSERT INTO file_image(old_name, new_name, category_id, size, path, remark, create_time, modify_time) " +
+            "VALUES (#{oldName}, #{newName}, #{categoryId}, #{size}, #{path}, #{remark}, now(), now())")
     int insertFileImage(FileImage fileImage);
 
     @Select({"<script>" +
-            "select * from file_image WHERE delete_flag = 1" +
+            "select a.*,b.category_name from file_image a left join file_image_category b on a.category_id = b.id WHERE a.delete_flag = 1" +
             "<if test=\"keyword != null and keyword != ''\">" +
-            "   and (old_name like concat('%',#{keyword},'%') or remark like concat('%',#{keyword},'%'))" +
+            "   and (a.old_name like concat('%',#{keyword},'%') or a.remark like concat('%',#{keyword},'%'))" +
+            "</if>" +
+            "<if test=\"categoryField != null and categoryField != ''\">" +
+            "   and a.category_id = #{categoryField}" +
             "</if>" +
             " order by ${sortField} ${sort}" +
             "</script>"})
     List<FileImage> findImageList(Map<String, Object> params);
 
+    @Select("select * from file_image where id = #{id}")
+    FileImage findFileImageById(Long id);
+
+    @Select("delete from file_image where id = #{id}")
+    FileImage deleteFileImageById(Long id);
+
     @Insert({"<script>" +
             "INSERT INTO coin_cmc_map(cmc_id,cmc_rank,name,symbol,slug,is_active,status," +
             "first_historical_data,last_historical_data,platform,modify_time)" +

+ 2 - 0
src/main/java/top/lvzhiqiang/service/impl/CoinApiConfigServiceImpl.java

@@ -59,6 +59,8 @@ public class CoinApiConfigServiceImpl implements CoinApiConfigService {
         for (CoinApiConfig coinApiConfig : coinApiConfigList) {
             if (coinApiConfig.getNameEn().equals("watchlist")) {
                 coinApiConfig.setTrackCategoryList(coinApiConfigMapper.findTrackCategoryList());
+            } else if (coinApiConfig.getNameEn().equals("image")) {
+                coinApiConfig.setOtherAttrList(coinApiConfigMapper.findFileImageCategoryList());
             }
         }
 

+ 3 - 2
src/main/java/top/lvzhiqiang/service/impl/CoinServiceImpl.java

@@ -1333,9 +1333,10 @@ public class CoinServiceImpl implements CoinService {
     }
 
     private void renderMainSearch4Image(List<FileImage> fileImageList) {
+        String ftpBaseurl = InitRunner.dicCodeMap.get("ftp_baseurl").getCodeValue();
         for (FileImage fileImage : fileImageList) {
-            String newPath = "<a target=\"_blank\" href=\" " + fileImage.getPath() + "\">" + fileImage.getPath() + "</a>";
-            fileImage.setPath(newPath);
+            String newPath = "<a target=\"_blank\" href=\" " + ftpBaseurl + fileImage.getPath() + "\">" + fileImage.getNewName() + "</a>";
+            fileImage.setNewName(newPath);
         }
     }
 

+ 122 - 32
src/main/java/top/lvzhiqiang/util/FtpUtil.java

@@ -1,11 +1,16 @@
 package top.lvzhiqiang.util;
 
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.net.ftp.FTP;
 import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPFile;
 import org.apache.commons.net.ftp.FTPReply;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketException;
 import java.time.LocalDate;
 import java.util.Random;
 
@@ -13,6 +18,7 @@ import java.util.Random;
  * 用于上传文件的工具类
  * https://juejin.cn/post/6894219004673294349<使用nginx和vsftp搭建图片服务器并使用Java上传图片到该图片服务器>
  */
+@Slf4j
 public class FtpUtil {
 
     private static String host;
@@ -35,41 +41,21 @@ public class FtpUtil {
         baseUrl = baseUrl_;
     }
 
-    public static boolean uploadFile(String filename, InputStream input) {
-        return uploadFile(host, port, userName, passWord, basePath, filename, input);
-    }
-
     /**
      * Description: 向FTP服务器上传文件
      *
-     * @param host     FTP服务器ip
-     * @param port     FTP服务器端口
-     * @param username FTP登录账号
-     * @param password FTP登录密码
-     * @param basePath FTP服务器基础目录,/home/ftpuser/images
-     * @param filename 上传到FTP服务器上的文件名
+     * @param fileName 上传到FTP服务器上的文件名
      * @param input    输入流
      * @return 成功返回true,否则返回false
      */
-    public static boolean uploadFile(String host, int port, String username, String password, String basePath, String filename, InputStream input) {
-        boolean result = false;
-        FTPClient ftpClient = new FTPClient();
+    public static boolean uploadFile(String fileName, InputStream input) {
+        FTPClient ftpClient = null;
+        boolean flag = false;
         try {
-            // 连接FTP服务器
-            //// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
-            ftpClient.connect(host, port);
-            // 登录
-            ftpClient.login(username, password);
-            int replyCode = ftpClient.getReplyCode();
-            if (!FTPReply.isPositiveCompletion(replyCode)) {
-                ftpClient.disconnect();
-                return result;
-            }
+            ftpClient = genFtpClient();
 
             // 设置为被动模式
             ftpClient.enterLocalPassiveMode();
-            // 设置编码格式为utf-8
-            ftpClient.setControlEncoding("UTF-8");
             // 设置上传文件的类型为二进制类型
             ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
             // 设置存储图片的文件夹
@@ -80,20 +66,54 @@ public class FtpUtil {
                     continue;
                 }
                 if (!ftpClient.changeWorkingDirectory(s)) {
+                    // 创建目录,一次只能创建一个目录
                     ftpClient.makeDirectory(s);
+                    // 重新指定工作路径
                     ftpClient.changeWorkingDirectory(s);
                 }
             }
+
             // 上传文件
-            if (!ftpClient.storeFile(filename, input)) {
-                return result;
+            flag = ftpClient.storeFile(fileName, input);
+        } catch (SocketException e) {
+            log.error("文件上传-连接错误,fileName={}", fileName, e);
+        } catch (Exception e) {
+            log.error("文件上传-失败,fileName={}", fileName, e);
+        } finally {
+            try {
+                if (ftpClient.isConnected()) {
+                    ftpClient.logout();
+                    ftpClient.disconnect();
+                }
+                if (input != null) {
+                    input.close();
+                }
+            } catch (IOException ioe) {
+                ioe.printStackTrace();
             }
+        }
 
-            input.close();
+        return flag;
+    }
 
-            result = true;
-        } catch (IOException e) {
-            e.printStackTrace();
+    /**
+     * Description: 从FTP服务器上传删除文件
+     *
+     * @param filePath
+     * @return 成功返回true,否则返回false
+     */
+    public static boolean delFile(String filePath) {
+        FTPClient ftpClient = null;
+        boolean flag = false;
+        try {
+            ftpClient = genFtpClient();
+
+            // 删除文件
+            flag = ftpClient.deleteFile(filePath);
+        } catch (SocketException e) {
+            log.error("文件删除-连接错误,filePath={}", filePath, e);
+        } catch (Exception e) {
+            log.error("文件删除-失败,filePath={}", filePath, e);
         } finally {
             if (ftpClient.isConnected()) {
                 try {
@@ -103,7 +123,77 @@ public class FtpUtil {
                 }
             }
         }
-        return result;
+
+        return flag;
+    }
+
+    /**
+     * Description: 从FTP服务器上传删除文件
+     *
+     * @param filePath
+     * @return 成功返回true,否则返回false
+     */
+    public static void downloadFile(File filePath, OutputStream is) {
+        FTPClient ftpClient = null;
+        try {
+            ftpClient = genFtpClient();
+
+            // 下载文件
+            // 转移到FTP服务器目录
+            ftpClient.changeWorkingDirectory(filePath.getParent());
+            FTPFile[] fs = ftpClient.listFiles();
+            String fileName = filePath.getName();
+            for (FTPFile ff : fs) {
+                if (ff.getName().equals(fileName)) {
+                    ftpClient.retrieveFile(ff.getName(), is);
+                }
+            }
+        } catch (SocketException e) {
+            log.error("文件下载-连接错误,filePath={}", filePath, e);
+        } catch (Exception e) {
+            log.error("文件下载-失败,filePath={}", filePath, e);
+        } finally {
+            if (ftpClient.isConnected()) {
+                try {
+                    ftpClient.logout();
+                    ftpClient.disconnect();
+                } catch (IOException ioe) {
+                }
+            }
+        }
+    }
+
+    public static FTPClient genFtpClient() throws IOException {
+        return genFtpClient(host, port, userName, passWord);
+    }
+
+    /**
+     *
+     * @param host_     FTP服务器ip
+     * @param port_     FTP服务器端口
+     * @param userName_ FTP登录账号
+     * @param passWord_ FTP登录密码
+     * @return
+     * @throws IOException
+     */
+    public static FTPClient genFtpClient(String host_,Integer port_,String userName_,String passWord_) throws IOException {
+        // 1.创建FTPClient对象
+        FTPClient ftpClient = new FTPClient();
+        // 2.保存FTP控制连接使用的字符集,必须在连接前设置
+        ftpClient.setControlEncoding("UTF-8");
+        // 3.连接FTP服务器,如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
+        ftpClient.connect(host_, port_);
+        // 4.登录
+        ftpClient.login(userName_, passWord_);
+        // 5.连接成功或者失败返回的状态码,如果reply返回230表示成功,如果返回530表示无密码或用户名错误或密码错误或用户权限问题。
+        int replyCode = ftpClient.getReplyCode();
+        if (!FTPReply.isPositiveCompletion(replyCode)) {
+            ftpClient.disconnect();
+
+            throw new IOException("FTP登录失败!");
+        }
+
+        return ftpClient;
     }
 
     /**

+ 23 - 3
src/main/resources/static/coin.html

@@ -82,6 +82,17 @@
         margin-bottom: 0.1em;
         white-space: nowrap;
     }
+
+    .quiet-loading, .uploadImgs-loading {
+        display: none;
+        position: absolute;
+        z-index: 999;
+        margin: auto;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -25%);
+    }
+
 </style>
 <script type="text/javascript">
     function show() {
@@ -235,12 +246,15 @@
             <button class="apis-quiet-div-button3" slideDiv="apis-quiet-content" pageO="prev">上一页</button>
             <button class="apis-quiet-div-button3" slideDiv="apis-quiet-content" pageO="next">下一页</button>
             <input type="text" style="width: 100px;padding-top: 3px;" id="apis-quiet-div-image-pageNo" value="1">
-            <input type="text" style="width: 100px;padding-top: 3px;" id="apis-quiet-div-image-pageSize" disabled="disabled" value="25">
+            <input type="text" style="width: 100px;padding-top: 3px;" id="apis-quiet-div-image-pageSize" disabled="disabled" value="20">
             <input type="text" style="width: 100px;padding-top: 3px;" id="apis-quiet-div-image-pages" disabled="disabled" value="999999">
             <input type="text" style="width: 100px;padding-top: 3px;" id="apis-quiet-div-image-keyword" placeholder="关键词">
+            <select id="apis-quiet-div-image-categoryField" style="height: 24px;">
+                <option value="">--</option>
+            </select>
             <select id="apis-quiet-div-image-sortField" style="height: 24px;">
-                <option value="create_time">创建时间</option>
-                <option value="size">图片大小</option>
+                <option value="a.create_time">创建时间</option>
+                <option value="a.size">图片大小</option>
             </select>
             <select id="apis-quiet-div-image-sort" style="height: 24px;">
                 <option value="desc">desc</option>
@@ -334,6 +348,7 @@
     <div id="apis-quiet-content" style="display: none;">
         total:<span class="contentSPAN">0</span>
         <table border="1" cellspacing="0">
+            <div class="quiet-loading"><img src='cover/loading.gif'></div>
             <thead>
             <tr class="contentTH">
             </tr>
@@ -345,9 +360,14 @@
     <hr/>
     <div style="display: flex;">
         <div style="margin-right:20px;">
+            <div class="uploadImgs-loading"><img src='cover/loading.gif'></div>
             <span class="font">uploadImgs</span>
             <span id="uploadImgsAlert" style="margin-left: 10px;font-size: 13px;"></span>
             <form method="post" action="coin/uploadImgs" enctype="multipart/form-data" onsubmit="return false;" id="uploadImgs">
+                <span>分类</span>
+                <select id="apis-quiet-div-uploadImgs-categoryField" style="height: 24px;" name="categoryId">
+                    <option value="">--</option>
+                </select>
                 <span>备注</span>
                 <input type="text" name="remark" placeholder="可为空"/>
                 <span>file</span>

+ 51 - 2
src/main/resources/static/js/my-coin.js

@@ -158,6 +158,13 @@ function initOther4Select() {
                             watchlistTrackCategoryStr += '<option value="' + obj2 + '">' + obj2 + '</option>';
                         });
                         $("#apis-quiet-div-watchlist-trackCategoryField").append(watchlistTrackCategoryStr);
+                    }else if (obj.nameEn === 'image'){
+                        var uploadImageCategoryStr = '';
+                        $.each(obj.otherAttrList, function (index2, obj2) {
+                            uploadImageCategoryStr += '<option value="' + obj2.id + '">' + obj2.categoryName + '</option>';
+                        });
+                        $("#apis-quiet-div-uploadImgs-categoryField").append(uploadImageCategoryStr);
+                        $("#apis-quiet-div-image-categoryField").append(uploadImageCategoryStr);
                     }
                 });
 
@@ -234,7 +241,7 @@ function handleSelectChange(objj) {
             $.each(returnEn, function (index, obj) {
                 theadStr += '<th returnEn="' + obj + '">' + returnCn[index] + '</th>';
             });
-            if (nameEn === 'watchlist') {
+            if (nameEn === 'watchlist' || nameEn === 'image') {
                 theadStr += '<th>操作</th>';
             }
 
@@ -330,6 +337,7 @@ function mainSearch(url, nameEn, slideDiv, needCustomFlag) {
         jsonData.keyword = $("#apis-quiet-div-image-keyword").val();
         jsonData.sortField = $("#apis-quiet-div-image-sortField").val();
         jsonData.sort = $("#apis-quiet-div-image-sort").val();
+        jsonData.categoryField = $("#apis-quiet-div-image-categoryField").val();
     } else if (nameEn === 'cmcmap') {
         jsonData.pageNo = $("#apis-quiet-div-cmcmap-pageNo").val();
         jsonData.pageSize = $("#apis-quiet-div-cmcmap-pageSize").val();
@@ -396,6 +404,10 @@ function mainSearch(url, nameEn, slideDiv, needCustomFlag) {
                         str += '<button class="apis-quiet-div-watchlist-detail" operationType="detail" symbolName="' + dataDetail.symbol + '">详情</button>';
                         str += '<button class="apis-quiet-div-watchlist-update" operationType="update" symbolName="' + dataDetail.symbol + '">编辑</button>';
                         str += '</td>';
+                    } else if (nameEn === 'image') {
+                        str += '<td style="padding: 0px 10px 0px 10px;">';
+                        str += '<button class="apis-quiet-div-image-delete" operationType="delete" symbolName="' + dataDetail.id + '">删除</button>';
+                        str += '</td>';
                     }
 
                     str += '</tr>';
@@ -405,6 +417,8 @@ function mainSearch(url, nameEn, slideDiv, needCustomFlag) {
                     $(".apis-quiet-div-watchlist-detail").unbind("click");
                     $(".apis-quiet-div-watchlist-update").unbind("click");
                     $(".watchlistpreview-top-close").unbind("click");
+                } else if (nameEn === 'image') {
+                    $(".apis-quiet-div-image-delete").unbind("click");
                 }
 
                 $('#' + slideDiv).find(".contentTD").html(str);
@@ -415,8 +429,10 @@ function mainSearch(url, nameEn, slideDiv, needCustomFlag) {
             }
         },
         beforeSend: function () {
+            $(".quiet-loading").css("display", "block");
         },
         complete: function () {
+            $(".quiet-loading").css("display", "none");
         },
         error: function (data) {
             //请求出错处理
@@ -517,6 +533,33 @@ function initContentEvent(nameEn) {
                 }
             });
         });
+    }else if (nameEn === 'image') {
+        $(".apis-quiet-div-image-delete").click(function () {
+            var symbol = $(this).attr("symbolName");
+            $.ajax({
+                url: "coin/deleteImgs/" + symbol, //请求的url地址
+                type: "get", //请求方式
+                async: true, //请求是否异步,默认为异步,这也是ajax重要特性
+                success: function (data) {
+                    //请求成功时处理
+                    if (data != null && $.trim(data) != "" && data.success) {
+                        $(".apis-quiet-div-button2").click();
+                    } else {
+                        alert(data.message);
+                    }
+                },
+                beforeSend: function () {
+                    $(".quiet-loading").css("display", "block");
+                },
+                complete: function () {
+                    $(".quiet-loading").css("display", "none");
+                },
+                error: function (data) {
+                    //请求出错处理
+                    alert('error:' + data);
+                }
+            });
+        });
     }
 }
 
@@ -529,16 +572,22 @@ function uploadImgsSubmit(){
         type: "post", //请求方式
         processData: false,// 告诉jQuery不要去处理发送的数据
         contentType: false,// 告诉jQuery不要去设置Content-Type请求头
-        async: false, //请求是否异步,默认为异步,这也是ajax重要特性
+        async: true, //请求是否异步,默认为异步,这也是ajax重要特性
         success: function (data) {
+            $(".uploadImgs-loading").css("display", "none");
             //请求成功时处理
             if (data != null && $.trim(data) != "" && data.success) {
                 $("#uploadImgsAlert").html(JSON.stringify(data.data));
+                var quietSelectOption = $("#apis-quiet-select option:selected");
+                if ($(quietSelectOption).attr("nameen") === 'image') {
+                    $(".apis-quiet-div-button2").click();
+                }
             } else {
                 $("#uploadImgsAlert").html(data.message);
             }
         },
         beforeSend: function () {
+            $(".uploadImgs-loading").css("display", "block");
         },
         complete: function () {
         },