提交 2e7a308b 编写于 作者: 智布道's avatar 智布道 👁

👽 jquery-confirm升级、优化评论管理列表等

上级 19d8be5c
@CHARSET "UTF-8";
/**
* MIT License
*
* Copyright (c) 2018 yadong.zhang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* 项目页面模板核心CSS
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
......@@ -3643,7 +3623,7 @@ ul.project_files li a i {
}
.input-group.date .input-group-addon {
border-radius: 0px !important;
border-radius: 0 !important;
background-color: #ffff !important
}
......@@ -4703,15 +4683,15 @@ ul.notifications {
}
.table {
margin-bottom: 0px !important;
margin-bottom: 0 !important;
}
.pagination {
margin: 0px 0;
margin: 0 0;
}
footer {
margin-left: 0px !important;
margin-left: 0 !important;
}
.fa-blank-right {
......@@ -4770,16 +4750,16 @@ small, .small {
ul, menu, dir {
display: block;
list-style-type: disc;
-webkit-margin-before: 0em;
-webkit-margin-after: 0em;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
-webkit-padding-start: 0px !important;
-webkit-margin-before: 0;
-webkit-margin-after: 0;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
-webkit-padding-start: 0 !important;
}
.wechat-users li {
font-size: 25px;
padding-right: 0px;
padding-right: 0;
}
.wechat-users li img {
......@@ -4812,52 +4792,15 @@ ul, menu, dir {
}
.option-img:hover {
-webkit-box-shadow: 0px 0px 18px #888888;
-moz-box-shadow: 0px 0px 18px #888888;
box-shadow: 0px 0px 18px #888888;
}
.chooseImg {
background: url("/assets/images/chooseImg_N.png");
display: block;
width: 65px;
height: 65px;
}
.chooseImg:hover {
background: url("/assets/images/chooseImg_S.png");
-webkit-box-shadow: 0 0 18px #888888;
-moz-box-shadow: 0 0 18px #888888;
box-shadow: 0 0 18px #888888;
}
.popover {
max-width: 100% !important;
}
.upload-wrap {
position: relative;
width: 60px;
height: 60px;
line-height: 80px;
overflow: hidden;
margin-right: 5px;
border: 1px dashed #cacbcc;
display: inline-block;
text-align: center;
background: url('/assets/images/upload.png') -6px -1px no-repeat;
}
.upload-wrap input[type=file], .upload-wrap button {
position: absolute;
top: 0;
left: 0;
font-size: 0;
width: 100%;
height: 100%;
outline: 0;
opacity: 0;
filter: alpha(opacity=0);
cursor: pointer;
}
.lately span {
display: inline-block;
margin-right: 5px;
......@@ -4874,7 +4817,7 @@ ul, menu, dir {
.radio label, .checkbox label {
min-height: 20px;
padding-left: 0px;
padding-left: 0;
margin-bottom: 0;
font-weight: 400;
cursor: pointer;
......@@ -4887,7 +4830,7 @@ ul, menu, dir {
::-webkit-scrollbar-thumb {
width: 10px;
border: 0px solid #000;
border: 0 solid #000;
background: #cbcbcb;
}
......@@ -4942,7 +4885,6 @@ ul, menu, dir {
}
/* wangEditor fullscreen */
@CHARSET "UTF-8";
.w-e-toolbar {
flex-wrap: wrap;
......@@ -4961,8 +4903,8 @@ ul, menu, dir {
position: fixed !important;
width: 100% !important;
height: 100% !important;
left: 0px !important;
top: 0px !important;
left: 0 !important;
top: 0 !important;
background-color: white;
z-index: 9999;
}
......@@ -5103,4 +5045,8 @@ ul, menu, dir {
opacity: 1;
}
.mask.load {
}
.pointer {
cursor: pointer;
}
\ No newline at end of file
......@@ -121,7 +121,7 @@ var zhyd = window.zhyd || {
$(".noticeNum").text(0);
return;
}
var tpl = '{{#data}}<li><a href="/comments"><span class="image"><img src="{{#avatar}}{{avatar}}{{/avatar}}{{^avatar}}/assets/images/user.png{{/avatar}}" alt="user avatar"></span> <span><span>{{nickname}}</span> <span class="time">{{createTimeString}}</span></span> <span class="message">点击查看&审核</span></a></li>{{/data}}';
var tpl = '{{#data}}<li><a href="/comments"><span class="image"><img src="{{#avatar}}{{avatar}}{{/avatar}}{{^avatar}}/assets/images/user.png{{/avatar}}" onerror="this.src=\'/assets/images/user.png\'" alt="user avatar"></span> <span><span>{{nickname}}</span> <span class="time">{{createTimeString}}</span></span> <span class="message">点击查看&审核</span></a></li>{{/data}}';
html = Mustache.render(tpl, json);
$box.before(html);
$(".noticeNum").text(json.data.length);
......
......@@ -58,7 +58,7 @@ $(".to-choose-info").click(function () {
if(validator.checkAll($publishForm)) {
$("#publishModal").modal('show');
}
})
});
// 点击保存
$(".publishBtn").click(function () {
......
......@@ -18,35 +18,60 @@
info: function (content, callback, delayTime) {
delayTime = delayTime ? "confirm|" + delayTime : "confirm|3000";
$.jqAlert({
icon: 'fa fa-info-circle',
title: '友情提示',
content: content,
confirmButton: '关闭',
type: 'green',
typeAnimated: true,
autoClose: delayTime,
confirm: callback
buttons: {
confirm: {
text: "关闭",
btnClass: 'btn-default',
action: callback
}
}
});
},
error: function (content, callback, delayTime) {
delayTime = delayTime ? "confirm|" + delayTime : "confirm|3000";
$.jqAlert({
icon: 'fa fa-exclamation-circle',
title: '警告',
content: content,
confirmButton: '关闭',
autoClose: delayTime,
confirm: callback
type: 'orange',
typeAnimated: true,
buttons: {
confirm: {
text: "关闭",
btnClass: 'btn-default',
action: callback
}
}
});
},
confirm: function (content, confirmCallback, cancelCallback, delayTime) {
delayTime = delayTime ? "cancel|" + delayTime : "cancel|5000";
$.jqConfirm({
confirmButtonClass: 'btn-success',
cancelButtonClass: 'btn-default',
title: '确认提示',
icon: 'fa fa-question-circle',
title: '确认?',
content: content,
autoClose: delayTime,
confirmButton: '确定',
cancelButton: '关闭',
confirm: confirmCallback,
cancel: cancelCallback
type: 'dark',
typeAnimated: true,
buttons: {
confirm: {
text: '确定',
btnClass: 'btn-green',
action: confirmCallback
},
cancel: {
text: '取消',
btnClass: 'btn-default',
action: cancelCallback
}
}
});
},
ajaxSuccessConfirm: function (json, callback, cancelCallback) {
......
......@@ -146,13 +146,15 @@
editable: false,
width: '200px',
formatter: function (code, row, index) {
return '<ul class="list-unstyled" style="max-width: 200px;">' +
'<li><a href="' + row.url + '" target="_blank"><img src="' + filterXSS(row.avatar) + '" style="width: 20px;border-radius: 50%;position: relative;top: -2px;"/>' + filterXSS(row.nickname) + '</a></li>' +
'<li>IP: <span style="color: #a9a9a9;">'+row.ip+'</span></li>' +
'<li>地址: <span style="color: #a9a9a9;">'+row.address+'</span></li>' +
// '<li>邮箱: <span style="color: #a9a9a9;">'+filterXSS(row.email)+'</span></li>' +
'<li>设备: <span style="color: #a9a9a9;">'+row.os + ' ' + row.browser +'</span></li>' +
'<li style="color: #a9a9a9;">'+row.createTimeString+'</li></ul>';
return '<ul class="list-unstyled">' +
'<li>' +
'<a href="' + row.url + '" target="_blank"><img src="' + filterXSS(row.avatar) + '" onerror="this.src=\'/assets/images/user.png\'" style="width: 20px;border-radius: 50%;position: relative;top: -2px;"/> ' + filterXSS(row.nickname) + '</a>' +
'<a href="javascript:void(0);" onclick="window.open(\'tencent://message/?uin=' + row.qq + '&amp;Menu=yes\')" rel="external nofollow" target="_blank"><i class="fa fa-qq fa-fw"></i></a>' +
'<a href="mailto:' + filterXSS(row.email) + '" rel="external nofollow" target="_blank"><i class="fa fa-envelope fa-fw"></i></a>' +
'</li>' +
'<li><i class="fa fa-address-book-o fa-fw"></i> <span style="color: #a9a9a9;">' + row.ip + ' | ' + row.address + '</span></li>' +
'<li><i class="fa fa-windows fa-fw"></i> <span style="color: #a9a9a9;">' + row.os + ' | ' + row.browser + '</span></li>' +
'<li><i class="fa fa-clock-o fa-fw"></i> <span style="color: #a9a9a9;">' + row.createTimeString + '</span></li></ul>';
}
}, {
field: 'content',
......
......@@ -136,9 +136,12 @@
</div>
<div role="tabpanel" class="tab-pane fade" id="tab_storage" aria-labelledby="storage-tab">
<form class="form-horizontal form-label-left" novalidate>
<div class="alert alert-info" role="alert" style="color: white">
<a href="#" class="close" data-dismiss="alert">&times;</a>
<i class="fa fa-info-circle fa-fw"></i>注意:系统<strong>暂不自持自动同步</strong>各个云存储空间中的文件,所以当切换云存储类型时可能会造成<strong class="red">部分图片不可用</strong>的情况!请悉知!
</div>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="title">存储类型 <span
class="required">*</span></label>
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="title">存储类型 <span class="required">*</span></label>
<div class="col-md-8 col-sm-8 col-xs-8">
<div class="checkbox">
<label for="storageType" style="margin-right: 10px"> <input type="radio" class="square" name="storageType" value="local" checked="checked"/> 本地 </label>
......@@ -361,8 +364,8 @@
</label>
<div class="col-md-6 col-sm-6 col-xs-12 fixed-radio-checkbox">
<ul class="list-unstyled list-inline">
<li><input type="radio" class="square" checked name="anonymous" value="1"> 开启 </li>
<li><input type="radio" class="square" name="anonymous" value="0"> 关闭 </li>
<li><label for="storageType" class="pointer"> <input type="radio" class="square" checked name="anonymous" value="1"> 开启 </label></li>
<li><label for="storageType" class="pointer"> <input type="radio" class="square" name="anonymous" value="0"> 关闭 </label></li>
</ul>
</div>
</div>
......@@ -370,8 +373,8 @@
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment">开启留言板评论 <i class="fa fa-question-circle" title="控制留言板页面的评论框显示情况"></i></label>
<div class="col-md-6 col-sm-6 col-xs-12 fixed-radio-checkbox">
<ul class="list-unstyled list-inline">
<li><input type="radio" class="square" checked name="comment" value="1"> 开启 </li>
<li><input type="radio" class="square" name="comment" value="0"> 关闭</li>
<li><label for="storageType" class="pointer"> <input type="radio" class="square" checked name="comment" value="1"> 开启 </label></li>
<li><label for="storageType" class="pointer"> <input type="radio" class="square" name="comment" value="0"> 关闭 </label></li>
</ul>
</div>
</div>
......@@ -490,8 +493,8 @@
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="maintenance">维护通知</label>
<div class="col-md-6 col-sm-6 col-xs-12 fixed-radio-checkbox">
<ul class="list-unstyled list-inline">
<li><input type="radio" class="square" checked name="maintenance" value="1"> 显示 </li>
<li><input type="radio" class="square" name="maintenance" value="0"> 关闭 </li>
<li><label for="storageType" class="pointer"> <input type="radio" class="square" checked name="maintenance" value="1"> 显示 </label> </li>
<li><label for="storageType" class="pointer"> <input type="radio" class="square" name="maintenance" value="0"> 关闭 </label></li>
</ul>
</div>
</div>
......@@ -499,13 +502,24 @@
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="maintenanceDate">维护日期</label>
<div class="col-md-6 col-sm-6 col-xs-12">
<div class='input-group date myDatepicker'>
<input type='text' class="form-control" required="required" readonly="readonly" id="maintenanceDate" name="maintenanceDate" placeholder="请输入维护日期"/>
<input type='text' class="form-control" readonly="readonly" id="maintenanceDate" name="maintenanceDate" placeholder="请输入维护日期"/>
<span class="input-group-addon">
<span class="fa fa-calendar"></span>
</span>
</div>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="maintenanceTime">维护用时</label>
<div class="col-md-6 col-sm-6 col-xs-12">
<div class='input-group'>
<input type='text' class="form-control" id="maintenanceTime" name="maintenanceTime" placeholder="请输入维护大约需要的时间"/>
<span class="input-group-addon">
</span>
</div>
</div>
</div>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="comment"></label>
<div class="col-md-6 col-sm-6 col-xs-12">
......@@ -544,13 +558,19 @@
<fieldset>
<legend style="padding-bottom: 0;"><h4>使用帮助<i class="fa fa-question-circle fa-fw"></i></h4>
</legend>
<ul class="list-unstyled">
<li><strong>serverName</strong> 改为自己的域名</li>
<li><strong>serverPath</strong> Nginx文件服务映射的服务器路径,同云存储中填写的“文件存储路径”</li>
<li><strong>serverReferers</strong> 防盗链的Referers,多个用空格分隔,支持通配符,比如:<code>*.zhyd.me zhyd.me</code></li>
<li><strong>serverLogoPath</strong> 触发防盗链后显示的默认图片,即当别人引用你网站中的图片时,会触发防盗链,对方网站中看到的就是 <code>serverLogoPath</code>对应的文件内容
</li>
</ul>
<dl>
<dt><i class="fa fa-info-circle fa-fw"></i>1. 替换配置文件中的指定内容</dt>
<dd><code>serverName</code> 改为自己的域名</dd>
<dd><code>serverPath</code> Nginx文件服务映射的服务器路径,同云存储中填写的“文件存储路径”</dd>
<dd><code>serverReferers</code> 防盗链的Referers,多个用空格分隔,支持通配符,比如:<code>*.zhyd.me zhyd.me</code></dd>
<dd><code>serverLogoPath</code> 触发防盗链后显示的默认图片,即当别人引用你网站中的图片时,会触发防盗链,对方网站中看到的就是 <code>serverLogoPath</code>对应的文件内容</dd>
</dl>
<dl>
<dt><i class="fa fa-info-circle fa-fw"></i>2. 添加Nginx配置</dt>
<dd>i. 将上方文本域修改后的内容保存为<code>**.conf</code>,放入到Nginx配置文件目录中</dd>
<dd>ii. 重启Nginx</dd>
<dd>iii. 尝试访问<code>serverName</code>检查Nginx是否配置成功</dd>
</dl>
</fieldset>
</div>
</div>
......@@ -563,6 +583,7 @@
<@footer>
<script type="text/javascript">
$(function () {
var oldStorageType;
$.ajax({
url: '/config/get',
type: 'POST',
......@@ -571,7 +592,8 @@
$("#myTabContent").find("input, select, textarea").each(function () {
clearText($(this), this.type, data);
});
oldStorageType = data.storageType;
changeMaintenance(data.maintenance && data.maintenance == 1, data.maintenance);
data.zfbPraiseCode && $("#zfbPraiseCodePreview").html('<img src="' + data.fileStoragePath + data.zfbPraiseCode + '" alt="支付宝赞赏码" class="img-responsive img-rounded auto-shake">');
data.wxPraiseCode && $("#wxPraiseCodePreview").html('<img src="' + data.fileStoragePath + data.wxPraiseCode + '" alt="微信赞赏码" class="img-responsive img-rounded auto-shake">');
}
......@@ -591,31 +613,60 @@
});
}
});
$("#tab_storage input[name=storageType]").on('ifChanged', function (event) {
$("#tab_storage input[name=storageType]").on('ifChecked', function (event) {
var $this = $(this);
var thisValue = $this.val();
if (!$("#" + thisValue).hasClass("hide")) {
return;
}
if(oldStorageType !== thisValue) {
$.alert.confirm("您确定要切换云存储类型吗?切换后原文件将不可访问!", function () {
oldStorageType = thisValue;
$(".storage-box").each(function () {
var $box = $(this);
if ($box.attr("id") === thisValue) {
$box.removeClass("hide").find("input").removeAttr("disabled").removeAttr("readonly");
} else {
$box.addClass("hide").find("input").attr("disabled", "disabled").attr("readonly", "readonly");
}
});
}, function () {
$("#tab_storage input[name=storageType]").each(function () {
var $this = $(this);
$this.iCheck((oldStorageType !== $this.val()) ? 'uncheck' : 'check');
});
});
}
});
if ($(this).is(':checked')) {
if (!$("#" + thisValue).hasClass("hide")) {
return;
}
$(".storage-box").each(function () {
var $box = $(this);
if ($box.attr("id") === thisValue) {
$box.removeClass("hide").find("input").removeAttr("disabled").removeAttr("readonly");
} else {
$box.addClass("hide").find("input").attr("disabled", "disabled").attr("readonly", "readonly");
}
$("#tab_setting input[name=maintenance]").on('ifChanged', function (event) {
changeMaintenance($(this).is(':checked'), $(this).val());
});
function changeMaintenance(checked, thisVal){
if (checked && thisVal == 1) {
$("#maintenanceDate, #maintenanceTime").each(function () {
var $this = $(this);
var $label = $this.parents("div.form-group").find("label");
$this.attr("required", "required");
$label.append('<span class="required">*</span>');
})
} else {
$("#maintenanceDate, #maintenanceTime").each(function () {
var $this = $(this);
var $span = $this.parents("div.form-group").find("label span");
$this.removeAttr("required");
$span.remove();
})
}
});
}
$("#aliyunBucketName, #aliyunEndpoint").change(function () {
var $fileUrl = $("#aliyunFileUrl");
var aliyunBucketName = $("#aliyunBucketName").val();
var aliyunEndpoint = $("#aliyunEndpoint").val();
if(aliyunBucketName && aliyunEndpoint) {
$fileUrl.val(aliyunBucketName + "." + aliyunEndpoint);
$fileUrl.val("https://" + aliyunBucketName + "." + aliyunEndpoint + "/");
}
});
......
......@@ -11,7 +11,7 @@
<link href="/assets/images/favicon.ico" rel="shortcut icon" type="image/x-icon">
<link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="https://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/jquery-confirm/2.5.1/jquery-confirm.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/jquery-confirm/3.3.2/jquery-confirm.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/fancybox/2.1.5/jquery.fancybox.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/toastr.js/2.0.3/css/toastr.min.css" rel="stylesheet">
......@@ -251,7 +251,7 @@
<#-- 添加或者修改列表记录时的弹窗模板 -->
<#macro addOrUpdateMOdal defaultTitle="">
<div class="modal fade" id="addOrUpdateModal" tabindex="-1" role="dialog" aria-labelledby="addroleLabel" data-backdrop="static">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
......
......@@ -9,7 +9,7 @@
<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js" type="text/javascript"></script>
<script src="https://cdn.bootcss.com/jquery_lazyload/1.9.7/jquery.lazyload.min.js" type="text/javascript"></script>
<script src="https://cdn.bootcss.com/jquery-confirm/2.5.1/jquery-confirm.min.js" type="text/javascript"></script>
<script src="https://cdn.bootcss.com/jquery-confirm/3.3.2/jquery-confirm.min.js" type="text/javascript"></script>
<script src="https://cdn.bootcss.com/fancybox/2.1.5/jquery.fancybox.min.js" type="text/javascript"></script>
<script src="https://cdn.bootcss.com/mustache.js/2.3.0/mustache.min.js" type="text/javascript"></script>
<script src="https://cdn.bootcss.com/js-xss/0.3.3/xss.min.js" type="text/javascript"></script>
......
......@@ -167,9 +167,9 @@
}
}, {
field: 'source',
title: '来源',
title: '来源 <i class="fa fa-question-circle-o" title="\'ADMIN\'表示管理员添加,\'AUTOMATIC\'表示用户自动添加"></i>',
editable: false,
width: '40px'
width: '60px'
}, {
field: 'email',
title: '联系方式',
......@@ -180,12 +180,8 @@
if(row.email){
html += '<a href="mailto:' + row.email + '" target="_blank" rel="external nofollow"><i class="fa fa fa-envelope fa-fw"></i></a>';
}
html = html ? html : '-';
html += '/';
if(row.qq){
html += '<a href="javascript:void(0);" target="_blank" onclick="window.open(\'tencent://message/?uin=' + row.qq + '&amp;Site=www.zhyd.me&amp;Menu=yes\')" rel="external nofollow"><i class="fa fa fa-qq fa-fw"></i></a>';
}else{
html += '-';
}
return html;
}
......
......@@ -195,6 +195,10 @@ public enum ConfigKeyEnum {
* 维护通知的日期
*/
MAINTENANCE_DATE("maintenanceDate"),
/**
* 维护通知大约需要的时间
*/
MAINTENANCE_TIME("maintenanceTime"),
/**
* 系统最后一次更新的日期
......
......@@ -9,7 +9,7 @@ package com.zyd.blog.business.enums;
*/
public enum ExtraCommentTypeEnum {
GUESTBOOK(1L, "/guestbook", "留言板 "),
GUESTBOOK(-1L, "/guestbook", "留言板 "),
LINKS(-2L, "/links", "友情链接 "),
ABOUT(-3L, "/about", "关于"),
ARTICLE(null, "/article/", ""),
......
package com.zyd.blog.plugin.file;
import com.alibaba.fastjson.JSONObject;
import com.zyd.blog.business.entity.File;
import com.zyd.blog.business.entity.User;
import com.zyd.blog.business.enums.ConfigKeyEnum;
import com.zyd.blog.business.service.BizFileService;
import com.zyd.blog.business.service.SysConfigService;
import com.zyd.blog.file.AliyunOssApiClient;
import com.zyd.blog.file.ApiClient;
import com.zyd.blog.file.LocalApiClient;
import com.zyd.blog.file.QiniuApiClient;
import com.zyd.blog.file.entity.VirtualFile;
import com.zyd.blog.file.exception.GlobalFileException;
import com.zyd.blog.framework.exception.ZhydException;
import com.zyd.blog.framework.holder.SpringContextHolder;
import com.zyd.blog.persistence.beans.BizFile;
......@@ -27,46 +29,54 @@ import java.util.Map;
*/
public class BaseFileUploader {
protected ApiClient getApiClient(String uploadType) {
ApiClient getApiClient(String uploadType) {
SysConfigService configService = SpringContextHolder.getBean(SysConfigService.class);
Map<String, Object> config = configService.getConfigs();
String storageType = null;
if (null == config || StringUtils.isEmpty((storageType = (String) config.get("storageType")))) {
if (null == config || StringUtils.isEmpty((storageType = (String) config.get(ConfigKeyEnum.STORAGE_TYPE.getKey())))) {
throw new ZhydException("[文件服务]当前系统暂未配置文件服务相关的内容!");
}
ApiClient res = null;
switch (storageType) {
case "local":
String localFileUrl = (String) config.get("localFileUrl"),
localFilePath = (String) config.get("localFilePath");
String localFileUrl = (String) config.get(ConfigKeyEnum.LOCAL_FILE_URL.getKey()),
localFilePath = (String) config.get(ConfigKeyEnum.LOCAL_FILE_PATH.getKey());
res = new LocalApiClient().init(localFileUrl, localFilePath, uploadType);
break;
case "qiniu":
String accessKey = (String) config.get("qiniuAccessKey"),
secretKey = (String) config.get("qiniuSecretKey"),
bucketName = (String) config.get("qiniuBucketName"),
baseUrl = (String) config.get("qiniuBasePath");
res = new QiniuApiClient().init(accessKey, secretKey, bucketName, baseUrl, uploadType);
String accessKey = (String) config.get(ConfigKeyEnum.QINIU_ACCESS_KEY.getKey()),
secretKey = (String) config.get(ConfigKeyEnum.QINIU_SECRET_KEY.getKey()),
qiniuBucketName = (String) config.get(ConfigKeyEnum.QINIU_BUCKET_NAME.getKey()),
baseUrl = (String) config.get(ConfigKeyEnum.QINIU_BASE_PATH.getKey());
res = new QiniuApiClient().init(accessKey, secretKey, qiniuBucketName, baseUrl, uploadType);
break;
case "aliyun":
String endpoint = (String) config.get(ConfigKeyEnum.ALIYUN_ENDPOINT.getKey()),
accessKeyId = (String) config.get(ConfigKeyEnum.ALIYUN_ACCESS_KEY.getKey()),
accessKeySecret = (String) config.get(ConfigKeyEnum.ALIYUN_ACCESS_KEY_SECRET.getKey()),
url = (String) config.get(ConfigKeyEnum.ALIYUN_FILE_URL.getKey()),
aliYunBucketName = (String) config.get(ConfigKeyEnum.ALIYUN_BUCKET_NAME.getKey());
res = new AliyunOssApiClient().init(endpoint, accessKeyId, accessKeySecret, url, aliYunBucketName, uploadType);
break;
case "youpaiyun":
break;
default:
break;
}
if (null == res) {
throw new GlobalFileException("[文件服务]当前系统暂未配置文件服务相关的内容!");
}
return res;
}
protected VirtualFile saveFile(VirtualFile virtualFile, boolean save, String uploadType) {
System.out.println(JSONObject.toJSONString(virtualFile));
VirtualFile saveFile(VirtualFile virtualFile, boolean save, String uploadType) {
if (save) {
BizFileService fileService = SpringContextHolder.getBean(BizFileService.class);
try {
SysConfigService configService = SpringContextHolder.getBean(SysConfigService.class);
Map<String, Object> config = configService.getConfigs();
String storageType = (String) config.get("storageType");
String storageType = (String) config.get(ConfigKeyEnum.STORAGE_TYPE.getKey());
BizFile fileInfo = BeanConvertUtil.doConvert(virtualFile, BizFile.class);
User sessionUser = SessionUtil.getUser();
......@@ -81,7 +91,7 @@ public class BaseFileUploader {
return virtualFile;
}
protected boolean removeFile(ApiClient apiClient, String filePath, String uploadType) {
boolean removeFile(ApiClient apiClient, String filePath, String uploadType) {
BizFileService fileService = SpringContextHolder.getBean(BizFileService.class);
File file = fileService.selectFileByPathAndUploadType(filePath, uploadType);
String fileKey = filePath;
......
......@@ -3,7 +3,7 @@ package com.zyd.blog.plugin.file;
import com.zyd.blog.file.ApiClient;
import com.zyd.blog.file.FileUploader;
import com.zyd.blog.file.entity.VirtualFile;
import com.zyd.blog.file.exception.QiniuApiException;
import com.zyd.blog.file.exception.GlobalFileException;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
......@@ -43,11 +43,10 @@ public class GlobalFileUploader extends BaseFileUploader implements FileUploader
@Override
public boolean delete(String filePath, String uploadType) {
if (StringUtils.isEmpty(filePath)) {
throw new QiniuApiException("[文件服务]文件删除失败,文件为空!");
throw new GlobalFileException("[文件服务]文件删除失败,文件为空!");
}
ApiClient apiClient = this.getApiClient(uploadType);
return this.removeFile(apiClient, filePath, uploadType);
}
}
......@@ -33,9 +33,14 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.4.RELEASE</version>
<version>${spring.web.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${aliyun.oss.version}</version>
</dependency>
</dependencies>
</project>
package com.zyd.blog.file;
import com.zyd.blog.file.alioss.api.OssApi;
import com.zyd.blog.file.entity.VirtualFile;
import com.zyd.blog.file.exception.OssApiException;
import com.zyd.blog.file.util.FileUtil;
import com.zyd.blog.file.util.StreamUtil;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/12 15:18
* @since 1.8
*/
public class AliyunOssApiClient extends BaseApiClient {
private static final String DEFAULT_PREFIX = "oneblog/";
private OssApi ossApi;
private String url;
private String bucketName;
private String pathPrefix;
public AliyunOssApiClient() {
super("阿里云OSS");
}
public AliyunOssApiClient init(String endpoint, String accessKeyId, String accessKeySecret, String url, String bucketName, String uploadType) {
ossApi = new OssApi(endpoint, accessKeyId, accessKeySecret);
this.url = url;
this.bucketName = bucketName;
this.pathPrefix = StringUtils.isEmpty(uploadType) ? DEFAULT_PREFIX : uploadType.endsWith("/") ? uploadType : uploadType + "/";
return this;
}
@Override
public VirtualFile uploadImg(InputStream is, String key) {
this.check();
this.createNewFileName(key, this.pathPrefix);
Date startTime = new Date();
try (InputStream uploadIs = StreamUtil.clone(is);
InputStream fileHashIs = StreamUtil.clone(is)) {
ossApi.uploadFile(uploadIs, this.newFileName, bucketName);
return new VirtualFile()
.setOriginalFileName(FileUtil.getName(key))
.setSuffix(this.suffix)
.setUploadStartTime(startTime)
.setUploadEndTime(new Date())
.setFilePath(this.newFileName)
.setFileHash(DigestUtils.md5DigestAsHex(fileHashIs))
.setFullFilePath(this.url + this.newFileName);
} catch (IOException e) {
throw new OssApiException("[" + this.storageType + "]文件上传失败:" + e.getMessage());
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public boolean removeFile(String key) {
this.check();
if (StringUtils.isEmpty(key)) {
throw new OssApiException("[" + this.storageType + "]删除文件失败:文件key为空");
}
try {
this.ossApi.deleteFile(key, bucketName);
return true;
} catch (Exception e) {
throw new OssApiException(e.getMessage());
}
}
@Override
public void check() {
if (null == ossApi) {
throw new OssApiException("[" + this.storageType + "]尚未配置阿里云OSS,文件上传功能暂时不可用!");
}
}
}
package com.zyd.blog.file;
import cn.hutool.core.date.DateUtil;
import com.zyd.blog.file.entity.VirtualFile;
import com.zyd.blog.file.exception.GlobalFileException;
import com.zyd.blog.file.exception.OssApiException;
import com.zyd.blog.file.exception.QiniuApiException;
import com.zyd.blog.file.util.FileUtil;
import com.zyd.blog.file.util.ImageUtil;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.Date;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/12 16:19
* @since 1.8
*/
public abstract class BaseApiClient implements ApiClient {
protected String storageType;
protected String newFileName;
protected String suffix;
public BaseApiClient(String storageType) {
this.storageType = storageType;
}
@Override
public VirtualFile uploadImg(MultipartFile file) {
this.check();
if (file == null) {
throw new OssApiException("[" + this.storageType + "]文件上传失败:文件不可为空");
}
try {
VirtualFile res = this.uploadImg(file.getInputStream(), file.getOriginalFilename());
VirtualFile imageInfo = ImageUtil.getInfo(file);
return res.setSize(imageInfo.getSize())
.setOriginalFileName(file.getOriginalFilename())
.setWidth(imageInfo.getWidth())
.setHeight(imageInfo.getHeight());
} catch (IOException e) {
throw new GlobalFileException("[" + this.storageType + "]文件上传失败:" + e.getMessage());
}
}
@Override
public VirtualFile uploadImg(File file) {
this.check();
if (file == null) {
throw new QiniuApiException("[" + this.storageType + "]文件上传失败:文件不可为空");
}
try {
InputStream is = new BufferedInputStream(new FileInputStream(file));
VirtualFile res = this.uploadImg(is, "temp." + FileUtil.getSuffix(file));
VirtualFile imageInfo = ImageUtil.getInfo(file);
return res.setSize(imageInfo.getSize())
.setOriginalFileName(file.getName())
.setWidth(imageInfo.getWidth())
.setHeight(imageInfo.getHeight());
} catch (FileNotFoundException e) {
throw new GlobalFileException("[" + this.storageType + "]文件上传失败:" + e.getMessage());
}
}
void createNewFileName(String key, String pathPrefix) {
this.suffix = FileUtil.getSuffix(key);
if (!FileUtil.isPicture(this.suffix)) {
throw new GlobalFileException("[" + this.storageType + "]只支持图片格式:[jpg, jpeg, png, gif, bmp]");
}
String fileName = DateUtil.format(new Date(), "yyyyMMddHHmmssSSS");
this.newFileName = pathPrefix + (fileName + this.suffix);
}
protected abstract void check();
}
package com.zyd.blog.file;
import cn.hutool.core.date.DateUtil;
import com.zyd.blog.file.entity.VirtualFile;
import com.zyd.blog.file.exception.LocalApiException;
import com.zyd.blog.file.util.FileUtil;
import com.zyd.blog.file.util.ImageUtil;
import com.zyd.blog.file.util.StreamUtil;
import org.springframework.util.DigestUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
/**
......@@ -20,7 +21,7 @@ import java.util.Date;
* @date 2019/2/11 15:06
* @since 1.8
*/
public class LocalApiClient implements ApiClient {
public class LocalApiClient extends BaseApiClient {
private static final String DEFAULT_PREFIX = "oneblog/";
private String url;
......@@ -28,83 +29,39 @@ public class LocalApiClient implements ApiClient {
private String pathPrefix;
public LocalApiClient() {
super("Nginx文件服务器");
}
public LocalApiClient init(String url, String rootPath, String uploadType) {
this.url = url;
this.rootPath = rootPath;
if (StringUtils.isEmpty(uploadType)) {
this.pathPrefix = DEFAULT_PREFIX;
} else {
this.pathPrefix = uploadType.endsWith("/") ? uploadType : uploadType + "/";
}
this.pathPrefix = StringUtils.isEmpty(uploadType) ? DEFAULT_PREFIX : uploadType.endsWith("/") ? uploadType : uploadType + "/";
return this;
}
@Override
public VirtualFile uploadImg(MultipartFile file) {
this.check();
if (file == null) {
throw new LocalApiException("[Nginx文件服务器]文件上传失败:文件不可为空");
}
try {
VirtualFile res = this.uploadImg(file.getInputStream(), file.getOriginalFilename());
VirtualFile imageInfo = ImageUtil.getInfo(file);
return res.setSize(imageInfo.getSize())
.setWidth(imageInfo.getWidth())
.setHeight(imageInfo.getHeight());
} catch (IOException e) {
throw new LocalApiException("[Nginx文件服务器]文件上传失败:" + e.getMessage());
}
}
@Override
public VirtualFile uploadImg(File file) {
this.check();
if (file == null) {
throw new LocalApiException("[Nginx文件服务器]文件上传失败:文件不可为空");
}
try {
InputStream is = new BufferedInputStream(new FileInputStream(file));
VirtualFile res = this.uploadImg(is, "temp." + FileUtil.getSuffix(file));
VirtualFile imageInfo = ImageUtil.getInfo(file);
return res.setSize(imageInfo.getSize())
.setWidth(imageInfo.getWidth())
.setHeight(imageInfo.getHeight());
} catch (FileNotFoundException e) {
throw new LocalApiException("[Nginx文件服务器]文件上传失败:" + e.getMessage());
}
}
@Override
public VirtualFile uploadImg(InputStream is, String key) {
this.check();
String suffix = FileUtil.getSuffix(key);
if (!FileUtil.isPicture(suffix)) {
throw new LocalApiException("[Nginx文件服务器]只支持图片格式:[jpg, jpeg, png, gif, bmp]");
}
this.createNewFileName(key, this.pathPrefix);
Date startTime = new Date();
String fileName = DateUtil.format(new Date(), "yyyyMMddHHmmssSSS");
key = pathPrefix + (fileName + suffix);
String realFilePath = this.rootPath + key;
String realFilePath = this.rootPath + this.newFileName;
FileUtil.checkFilePath(realFilePath);
try (InputStream copyIs = is;
try (InputStream uploadIs = StreamUtil.clone(is);
InputStream fileHashIs = StreamUtil.clone(is);
FileOutputStream fos = new FileOutputStream(realFilePath)) {
StreamUtils.copy(copyIs, fos);
String fileHash = DigestUtils.md5DigestAsHex(copyIs);
FileCopyUtils.copy(uploadIs, fos);
return new VirtualFile()
.setSize(is.available())
.setOriginalFileName(FileUtil.getName(key))
.setSuffix(suffix)
.setSuffix(this.suffix)
.setUploadStartTime(startTime)
.setUploadEndTime(new Date())
.setFilePath(key)
.setFileHash(fileHash)
.setFullFilePath(this.url + key);
.setFilePath(this.newFileName)
.setFileHash(DigestUtils.md5DigestAsHex(fileHashIs))
.setFullFilePath(this.url + this.newFileName);
} catch (IOException e) {
throw new LocalApiException("[Nginx文件服务器]文件上传失败:" + e.getMessage());
throw new LocalApiException("[" + this.storageType + "]文件上传失败:" + e.getMessage());
} finally {
if (is != null) {
try {
......@@ -120,22 +77,23 @@ public class LocalApiClient implements ApiClient {
public boolean removeFile(String key) {
this.check();
if (StringUtils.isEmpty(key)) {
throw new LocalApiException("[Nginx文件服务器]删除文件失败:文件key为空");
throw new LocalApiException("[" + this.storageType + "]删除文件失败:文件key为空");
}
File file = new File(this.rootPath + key);
if (!file.exists()) {
throw new LocalApiException("[Nginx文件服务器]删除文件失败:文件不存在[" + this.rootPath + key + "]");
throw new LocalApiException("[" + this.storageType + "]删除文件失败:文件不存在[" + this.rootPath + key + "]");
}
try {
return file.delete();
} catch (Exception e) {
throw new LocalApiException("[Nginx文件服务器]删除文件失败:" + e.getMessage());
throw new LocalApiException("[" + this.storageType + "]删除文件失败:" + e.getMessage());
}
}
private void check() {
@Override
public void check() {
if (StringUtils.isEmpty(url) || StringUtils.isEmpty(rootPath)) {
throw new LocalApiException("[Nginx文件服务器]尚未配置Nginx文件服务器,文件上传功能暂时不可用!");
throw new LocalApiException("[" + this.storageType + "]尚未配置Nginx文件服务器,文件上传功能暂时不可用!");
}
}
}
package com.zyd.blog.file;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.qiniu.common.QiniuException;
import com.qiniu.common.Zone;
......@@ -13,11 +12,8 @@ import com.qiniu.util.Auth;
import com.qiniu.util.StringUtils;
import com.zyd.blog.file.entity.VirtualFile;
import com.zyd.blog.file.exception.QiniuApiException;
import com.zyd.blog.file.util.FileUtil;
import com.zyd.blog.file.util.ImageUtil;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.io.InputStream;
import java.util.Date;
/**
......@@ -28,7 +24,7 @@ import java.util.Date;
* @date 2018/4/16 16:26
* @since 1.0
*/
public class QiniuApiClient implements ApiClient {
public class QiniuApiClient extends BaseApiClient {
private static final String DEFAULT_PREFIX = "oneblog/";
......@@ -39,84 +35,31 @@ public class QiniuApiClient implements ApiClient {
private String pathPrefix;
public QiniuApiClient() {
super("七牛云");
}
public QiniuApiClient init(String accessKey, String secretKey, String bucketName, String baseUrl, String uploadType) {
if (StringUtils.isNullOrEmpty(accessKey) || StringUtils.isNullOrEmpty(secretKey) ||
StringUtils.isNullOrEmpty(bucketName) || StringUtils.isNullOrEmpty(baseUrl)) {
throw new QiniuApiException("[七牛云]文件上传失败,系统当前暂不支持七牛云文件上传!");
}
this.accessKey = accessKey;
this.secretKey = secretKey;
this.bucket = bucketName;
this.path = baseUrl;
if (StringUtils.isNullOrEmpty(uploadType)) {
this.pathPrefix = DEFAULT_PREFIX;
} else {
this.pathPrefix = uploadType.endsWith("/") ? uploadType : uploadType + "/";
}
this.pathPrefix = StringUtils.isNullOrEmpty(uploadType) ? DEFAULT_PREFIX : uploadType.endsWith("/") ? uploadType : uploadType + "/";
return this;
}
/**
* 上传图片
*
* @param file 图片
* @return 上传后的路径
*/
public VirtualFile uploadImg(MultipartFile file) {
if (file == null) {
throw new QiniuApiException("[七牛云]文件上传失败:文件不可为空");
}
try {
VirtualFile res = this.uploadImg(file.getInputStream(), file.getOriginalFilename());
VirtualFile imageInfo = ImageUtil.getInfo(file);
return res.setSize(imageInfo.getSize())
.setOriginalFileName(file.getOriginalFilename())
.setWidth(imageInfo.getWidth())
.setHeight(imageInfo.getHeight());
} catch (IOException e) {
throw new QiniuApiException("[七牛云]文件上传失败:" + e.getMessage());
}
}
@Override
public VirtualFile uploadImg(File file) {
this.check();
if (file == null) {
throw new QiniuApiException("[七牛云]文件上传失败:文件不可为空");
}
try {
InputStream is = new BufferedInputStream(new FileInputStream(file));
VirtualFile res = this.uploadImg(is, "temp." + FileUtil.getSuffix(file));
VirtualFile imageInfo = ImageUtil.getInfo(file);
return res.setSize(imageInfo.getSize())
.setOriginalFileName(file.getName())
.setWidth(imageInfo.getWidth())
.setHeight(imageInfo.getHeight());
} catch (FileNotFoundException e) {
throw new QiniuApiException("[七牛云]文件上传失败:" + e.getMessage());
}
}
/**
* 上传图片
*
* @param is 图片流
* @param key 图片文件名(非上传后的文件名)
* @param is 图片流
* @param key 图片文件名(非上传后的文件名)
* @return 上传后的路径
*/
@Override
public VirtualFile uploadImg(InputStream is, String key) {
this.check();
String suffix = FileUtil.getSuffix(key);
if (!FileUtil.isPicture(suffix)) {
throw new QiniuApiException("[七牛云]只支持图片格式:[jpg, jpeg, png, gif, bmp]");
}
this.createNewFileName(key, this.pathPrefix);
Date startTime = new Date();
String fileName = DateUtil.format(new Date(), "yyyyMMddHHmmssSSS");
String newKey = pathPrefix + (fileName + suffix);
//Zone.zone0:华东
//Zone.zone1:华北
//Zone.zone2:华南
......@@ -124,23 +67,23 @@ public class QiniuApiClient implements ApiClient {
Configuration cfg = new Configuration(Zone.autoZone());
UploadManager uploadManager = new UploadManager(cfg);
try {
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
Response response = uploadManager.put(is, newKey, upToken, null, null);
Auth auth = Auth.create(this.accessKey, this.secretKey);
String upToken = auth.uploadToken(this.bucket);
Response response = uploadManager.put(is, this.newFileName, upToken, null, null);
//解析上传成功的结果
DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
return new VirtualFile()
.setOriginalFileName(key)
.setSuffix(suffix)
.setSuffix(this.suffix)
.setUploadStartTime(startTime)
.setUploadEndTime(new Date())
.setFilePath(putRet.key)
.setFileHash(putRet.hash)
.setFullFilePath(path + "/" + putRet.key);
.setFullFilePath(this.path + putRet.key);
} catch (QiniuException ex) {
throw new QiniuApiException("[七牛云]文件上传失败:" + ex.error());
throw new QiniuApiException("[" + this.storageType + "]文件上传失败:" + ex.error());
}
}
......@@ -154,27 +97,28 @@ public class QiniuApiClient implements ApiClient {
this.check();
if (StringUtils.isNullOrEmpty(key)) {
throw new QiniuApiException("[七牛云]删除文件失败:文件key为空");
throw new QiniuApiException("[" + this.storageType + "]删除文件失败:文件key为空");
}
Auth auth = Auth.create(accessKey, secretKey);
Auth auth = Auth.create(this.accessKey, this.secretKey);
Configuration config = new Configuration(Zone.autoZone());
BucketManager bucketManager = new BucketManager(auth, config);
try {
Response re = bucketManager.delete(bucket, key);
Response re = bucketManager.delete(this.bucket, key);
return re.isOK();
} catch (QiniuException e) {
Response r = e.response;
throw new QiniuApiException("[七牛云]删除文件发生异常:" + r.toString());
throw new QiniuApiException("[" + this.storageType + "]删除文件发生异常:" + r.toString());
}
}
private void check() {
if (StringUtils.isNullOrEmpty(accessKey) || StringUtils.isNullOrEmpty(secretKey) || StringUtils.isNullOrEmpty(bucket)) {
throw new QiniuApiException("[七牛云]尚未配置七牛云,文件上传功能暂时不可用!");
@Override
public void check() {
if (StringUtils.isNullOrEmpty(this.accessKey) || StringUtils.isNullOrEmpty(this.secretKey) || StringUtils.isNullOrEmpty(this.bucket)) {
throw new QiniuApiException("[" + this.storageType + "]尚未配置七牛云,文件上传功能暂时不可用!");
}
}
public String getPath() {
return path;
return this.path;
}
}
package com.zyd.blog.file.alioss.api;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.*;
import com.zyd.blog.file.alioss.entity.BucketEntity;
import com.zyd.blog.file.alioss.entity.CorsRoleEntity;
import com.zyd.blog.file.alioss.entity.ObjectsRequestEntity;
import com.zyd.blog.file.alioss.entity.RefererEntity;
import com.zyd.blog.file.exception.OssApiException;
import org.springframework.util.CollectionUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2019/2/12 15:49
* @since 1.8
*/
public class OssApi {
private OSSClient client;
public OssApi(OSSClient client) {
this.client = client;
}
public OssApi(String endpoint, String accessKeyId, String accessKeySecret) {
this.client = new OSSClient(endpoint, accessKeyId, accessKeySecret);
}
/**
* 授权访问文件的URL
*
* @param fileName 待授权的文件名
* @param bucketName 存储空间
* @param expirationTime 授权失效时间,单位秒
*/
public String authFile(String fileName, String bucketName, long expirationTime) {
try {
if (!this.client.doesBucketExist(bucketName)) {
throw new OssApiException("[阿里云OSS] 无法授权访问文件的URL!Bucket不存在:" + bucketName);
}
if (!this.client.doesObjectExist(bucketName, fileName)) {
throw new OssApiException("[阿里云OSS] 文件授权失败!文件不存在:" + bucketName + "/" + fileName);
}
// 设置URL过期时间为1小时
Date expiration = new Date(new Date().getTime() + expirationTime * 1000);
// 生成URL
return this.client.generatePresignedUrl(bucketName, fileName, expiration).toString();
} finally {
this.shutdown();
}
}
/**
* 判断文件是否存在
*
* @param fileName OSS中保存的文件名
* @param bucketName 存储空间
*/
public boolean isExistFile(String fileName, String bucketName) {
try {
if (!this.client.doesBucketExist(bucketName)) {
throw new OssApiException("[阿里云OSS] Bucket不存在:" + bucketName);
}
return this.client.doesObjectExist(bucketName, fileName);
} finally {
this.shutdown();
}
}
/**
* 获取指定bucket下的文件的访问权限
*
* @param fileName OSS中保存的文件名
* @param bucketName 存储空间
* @return
*/
public ObjectPermission getFileAcl(String fileName, String bucketName) {
try {
if (!this.client.doesBucketExist(bucketName)) {
throw new OssApiException("[阿里云OSS] 无法获取文件的访问权限!Bucket不存在:" + bucketName);
}
if (!this.client.doesObjectExist(bucketName, fileName)) {
throw new OssApiException("[阿里云OSS] 无法获取文件的访问权限!文件不存在:" + bucketName + "/" + fileName);
}
return this.client.getObjectAcl(bucketName, fileName).getPermission();
} finally {
this.shutdown();
}
}
/**
* 获取文件列表
*
* @param bucketName 存储空间名
* @param request 查询条件
* @return 文件列表
*/
public List<OSSObjectSummary> listFile(String bucketName, ObjectsRequestEntity request) {
try {
if (!this.client.doesBucketExist(bucketName)) {
throw new OssApiException("[阿里云OSS] 无法获取文件列表!Bucket不存在:" + bucketName);
}
ListObjectsRequest listRequest = new ListObjectsRequest(bucketName);
if (null != request) {
listRequest.withDelimiter(request.getDelimiter())
.withEncodingType(request.getEncodingType())
.withMarker(request.getMarker())
.withMaxKeys(request.getMaxKeys())
.withPrefix(request.getPrefix());
}
// 列举Object
ObjectListing objectListing = this.client.listObjects(listRequest);
return objectListing.getObjectSummaries();
} finally {
this.shutdown();
}
}
/**
* 修改指定bucket下的文件的访问权限
*
* @param fileName OSS中保存的文件名
* @param bucketName 保存文件的目标bucket
* @param acl 权限
*/
public void setFileAcl(String fileName, String bucketName, CannedAccessControlList acl) {
try {
boolean exists = this.client.doesBucketExist(bucketName);
if (!exists) {
throw new OssApiException("[阿里云OSS] 无法修改文件的访问权限!Bucket不存在:" + bucketName);
}
if (!this.client.doesObjectExist(bucketName, fileName)) {
throw new OssApiException("[阿里云OSS] 无法修改文件的访问权限!文件不存在:" + bucketName + "/" + fileName);
}
this.client.setObjectAcl(bucketName, fileName, acl);
} finally {
this.shutdown();
}
}
/**
* 删除文件
*
* @param bucketName 保存文件的目标bucket
* @param fileName OSS中保存的文件名
*/
public void deleteFile(String fileName, String bucketName) {
try {
boolean exists = this.client.doesBucketExist(bucketName);
if (!exists) {
throw new OssApiException("[阿里云OSS] 文件删除失败!Bucket不存在:" + bucketName);
}
if (!this.client.doesObjectExist(bucketName, fileName)) {
throw new OssApiException("[阿里云OSS] 文件删除失败!文件不存在:" + bucketName + "/" + fileName);
}
this.client.deleteObject(bucketName, fileName);
} finally {
this.shutdown();
}
}
/**
* 创建存储空间
*
* @param bucketName 存储空间名称
*/
public void createBucket(String bucketName) {
try {
boolean exists = this.client.doesBucketExist(bucketName);
if (exists) {
throw new OssApiException("[阿里云OSS] Bucket创建失败!Bucket名称[" + bucketName + "]已被使用!");
}
// -- 创建指定类型的Bucket,请使用Java SDK 2.6.0及以上版本。
CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
// 设置bucket权限为公共读,默认是私有读写
createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
// 设置bucket存储类型为低频访问类型,默认是标准类型
createBucketRequest.setStorageClass(StorageClass.IA);
this.client.createBucket(createBucketRequest);
} finally {
this.shutdown();
}
}
/**
* 设置bucket的访问权限
*
* @param bucket bucket
*/
public void setBucketAcl(BucketEntity bucket) {
try {
if (!this.client.doesBucketExist(bucket.getBucketName())) {
throw new OssApiException("[阿里云OSS] 无法修改Bucket的访问权限!Bucket不存在:" + bucket.getBucketName());
}
this.client.setBucketAcl(bucket.getBucketName(), bucket.getAcl());
} finally {
this.shutdown();
}
}
/**
* 跨域访问管理:跨域资源共享(CORS)允许web端的应用程序访问不属于本域的资源
*
* @param corsRole 跨域规则
*/
public void setBucketCors(CorsRoleEntity corsRole) {
try {
if (!this.client.doesBucketExist(corsRole.getBucketName())) {
throw new OssApiException("[阿里云OSS] 无法修改Bucket的跨域设置!Bucket不存在:" + corsRole.getBucketName());
}
SetBucketCORSRequest request = new SetBucketCORSRequest(corsRole.getBucketName());
//CORS规则的容器,每个bucket最多允许10条规则
ArrayList<SetBucketCORSRequest.CORSRule> putCorsRules = new ArrayList<>();
SetBucketCORSRequest.CORSRule corRule = new SetBucketCORSRequest.CORSRule();
corRule.setAllowedMethods(corsRole.getAllowedMethod());
corRule.setAllowedOrigins(corsRole.getAllowedOrigin());
corRule.setAllowedHeaders(corsRole.getAllowedHeader());
corRule.setExposeHeaders(corsRole.getExposedHeader());
//指定浏览器对特定资源的预取(OPTIONS)请求返回结果的缓存时间,单位为秒。
corRule.setMaxAgeSeconds(corsRole.getMaxAgeSeconds());
//最多允许10条规则
putCorsRules.add(corRule);
request.setCorsRules(putCorsRules);
this.client.setBucketCORS(request);
} finally {
this.shutdown();
}
}
/**
* 创建模拟文件夹本质上来说是创建了一个名字以“/”结尾的文件;<br>
* 对于这个文件照样可以上传下载,只是控制台会对以“/”结尾的文件以文件夹的方式展示;<br>
* 多级目录创建最后一级即可,比如dir1/dir2/dir3/,创建dir1/dir2/dir3/即可,dir1/、dir1/dir2/不需要创建;
*
* @param folder 目录名
* @param bucketName 存储空间
*/
public void createFolder(String folder, String bucketName) throws OssApiException {
try {
if (null == bucketName) {
throw new OssApiException("[阿里云OSS] 尚未指定Bucket!");
}
if (!this.client.doesBucketExist(bucketName)) {
throw new OssApiException("[阿里云OSS] 无法创建目录!Bucket不存在:" + bucketName);
}
folder = folder.endsWith("/") ? folder : folder + "/";
this.client.putObject(bucketName, folder, new ByteArrayInputStream(new byte[0]));
} finally {
this.shutdown();
}
}
/**
* 批量设置Referer白名单
*
* @param refererEntity refererEntity
*/
public void addReferers(RefererEntity refererEntity) {
try {
if (!this.client.doesBucketExist(refererEntity.getBucketName())) {
throw new OssApiException("[阿里云OSS] 无法设置Referer白名单!Bucket不存在:" + refererEntity.getBucketName());
}
if (CollectionUtils.isEmpty(refererEntity.getRefererList())) {
return;
}
BucketReferer br = new BucketReferer(true, refererEntity.getRefererList());
this.client.setBucketReferer(refererEntity.getBucketName(), br);
} finally {
this.shutdown();
}
}
/**
* 清空Referer白名单
*
* @param bucketName 存储空间名
*/
public void removeReferers(String bucketName) {
try {
if (!this.client.doesBucketExist(bucketName)) {
throw new OssApiException("[阿里云OSS] 无法清空Referer白名单!Bucket不存在:" + bucketName);
}
// 默认允许referer字段为空,且referer白名单为空。
BucketReferer br = new BucketReferer();
client.setBucketReferer(bucketName, br);
} finally {
this.shutdown();
}
}
/**
* 获取Referer白名单
*
* @param bucketName 存储空间名
*/
public List<String> getReferers(String bucketName) {
try {
if (!this.client.doesBucketExist(bucketName)) {
throw new OssApiException("[阿里云OSS] 无法获取Referer白名单!Bucket不存在:" + bucketName);
}
BucketReferer br = this.client.getBucketReferer(bucketName);
return br.getRefererList();
} finally {
this.shutdown();
}
}
/**
* @param localFile 待上传的文件
* @param fileName 文件名:最终保存到云端的文件名
* @param bucket 需要上传到的目标bucket
*/
public String uploadFile(File localFile, String fileName, String bucket) {
try {
InputStream inputStream = new FileInputStream(localFile);
return this.uploadFile(inputStream, fileName, bucket);
} catch (Exception e) {
throw new OssApiException("[阿里云OSS] 文件上传失败!" + localFile, e);
} finally {
this.shutdown();
}
}
/**
* @param inputStream 待上传的文件流
* @param fileName 文件名:最终保存到云端的文件名
* @param bucketName 需要上传到的目标bucket
*/
public String uploadFile(InputStream inputStream, String fileName, String bucketName) {
try {
if (!this.client.doesBucketExist(bucketName)) {
throw new OssApiException("[阿里云OSS] 无法上传文件!Bucket不存在:" + bucketName);
}
PutObjectResult result = this.client.putObject(bucketName, fileName, inputStream);
return result.getETag();
} finally {
this.shutdown();
}
}
private void shutdown() {
this.client.shutdown();
}
}
package com.zyd.blog.file.alioss.entity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2017/7/12 10:29
* @since 1.8
*/
@Data
@EqualsAndHashCode(callSuper = false)
public abstract class AbstractEntity {
private String bucketName;
public AbstractEntity() {
}
public AbstractEntity(String bucketName) {
this.bucketName = bucketName;
}
}
package com.zyd.blog.file.alioss.entity;
import com.aliyun.oss.model.CannedAccessControlList;
import lombok.Data;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2017/7/12 10:29
* @since 1.8
*/
@Data
public class BucketEntity extends AbstractEntity {
/*
* 私有读写 CannedAccessControlList.Private <br>
* 公共读私有写 CannedAccessControlList.PublicRead <br>
* 公共读写 CannedAccessControlList.PublicReadWrite
*/
private CannedAccessControlList acl;
public BucketEntity(String bucketName) {
super(bucketName);
}
public BucketEntity setAcl(CannedAccessControlList acl) {
this.acl = acl;
return this;
}
}
package com.zyd.blog.file.alioss.entity;
import lombok.Data;
import java.util.ArrayList;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2017/7/12 10:29
* @since 1.8
*/
@Data
public class CorsRoleEntity extends AbstractEntity {
private ArrayList<String> allowedOrigin; // 指定允许跨域请求的来源
private ArrayList<String> allowedMethod; // 指定允许的跨域请求方法(GET/PUT/DELETE/POST/HEAD)
private ArrayList<String> allowedHeader; // 控制在OPTIONS预取指令中Access-Control-Request-Headers头中指定的header是否允许
private ArrayList<String> exposedHeader; // 指定允许用户从应用程序中访问的响应头
private int maxAgeSeconds; // 指定浏览器对特定资源的预取(OPTIONS)请求返回结果的缓存时间,单位为秒
public CorsRoleEntity(String bucket) {
super(bucket);
this.maxAgeSeconds = 300;
}
public CorsRoleEntity setAllowedOrigin(ArrayList<String> allowedOrigin) {
this.allowedOrigin = allowedOrigin;
return this;
}
public CorsRoleEntity setAllowedMethod(ArrayList<String> allowedMethod) {
this.allowedMethod = allowedMethod;
return this;
}
public CorsRoleEntity setAllowedHeader(ArrayList<String> allowedHeader) {
this.allowedHeader = allowedHeader;
return this;
}
public CorsRoleEntity setExposedHeader(ArrayList<String> exposedHeader) {
this.exposedHeader = exposedHeader;
return this;
}
public void setMaxAgeSeconds(int maxAgeSeconds) {
this.maxAgeSeconds = maxAgeSeconds;
}
}
package com.zyd.blog.file.alioss.entity;
import lombok.Data;
/**
* https://help.aliyun.com/document_detail/32015.html?spm=5176.doc32021.6.665.PqGkRT
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2017/7/12 10:29
* @since 1.8
*/
@Data
public class ObjectsRequestEntity {
/*
* 限定返回的object key必须以prefix作为前缀
*/
private String prefix;
/*
* 设定结果从marker之后按字母排序的第一个开始返回
*/
private String marker;
/*
* 限定此次返回object的最大数,如果不设定,默认为100,max-keys取值不能大于1000
*/
private Integer maxKeys;
/*
* 是一个用于对Object名字进行分组的字符。
* 所有名字包含指定的前缀且第一次出现delimiter字符之间的object作为一组元素——CommonPrefixes
*/
private String delimiter;
/*
* 请求响应体中Object名称采用的编码方式,目前支持url。
*/
private String encodingType;
public ObjectsRequestEntity setPrefix(String prefix) {
this.prefix = prefix;
return this;
}
public ObjectsRequestEntity setMarker(String marker) {
this.marker = marker;
return this;
}
public ObjectsRequestEntity setMaxKeys(Integer maxKeys) {
this.maxKeys = maxKeys;
return this;
}
public ObjectsRequestEntity setDelimiter(String delimiter) {
this.delimiter = delimiter;
return this;
}
public ObjectsRequestEntity setEncodingType(String encodingType) {
this.encodingType = encodingType;
return this;
}
}
package com.zyd.blog.file.alioss.entity;
import lombok.Data;
import java.util.List;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2017/7/12 10:29
* @since 1.8
*/
@Data
public class RefererEntity extends AbstractEntity {
List<String> refererList;
public RefererEntity(String bucketName) {
super(bucketName);
}
public void setRefererList(List<String> refererList) {
this.refererList = refererList;
}
}
package com.zyd.blog.file.exception;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2017/7/12 10:29
* @since 1.8
*/
public class OssApiException extends RuntimeException {
public OssApiException(String message) {
super(message);
}
public OssApiException(String message, Throwable cause) {
super(message, cause);
}
}
......@@ -2,7 +2,6 @@ package com.zyd.blog.file.util;
import com.zyd.blog.file.entity.VirtualFile;
import com.zyd.blog.file.exception.GlobalFileException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
......
package com.zyd.blog.file.util;
import com.zyd.blog.file.exception.GlobalFileException;
import java.io.*;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0
* @website https://www.zhyd.me
* @date 2017/7/12 10:29
* @since 1.8
*/
public class StreamUtil {
/**
* 将InputStream转换为字符串
*
* @param is InputStream
* @return
*/
public static String toString(InputStream is) {
return toString(is, "UTF-8");
}
/**
* 将InputStream转换为字符串
*
* @param is InputStream
* @return
*/
public static String toString(InputStream is, String encoding) {
if (null == is) {
return null;
}
encoding = encoding == null ? "UTF-8" : encoding;
StringBuilder fileContent = new StringBuilder();
try (
InputStream inputStream = is;
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, encoding))
) {
String line = "";
while ((line = reader.readLine()) != null) {
fileContent.append(line);
fileContent.append("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return fileContent.toString();
}
/**
* 复制InputStream
*
* @param is InputStream
* @return
*/
public static InputStream clone(InputStream is) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
return new ByteArrayInputStream(baos.toByteArray());
} catch (IOException e) {
throw new GlobalFileException("无法复制当前文件流!", e);
}
}
}
package com.zyd.blog.file;
import com.zyd.blog.file.alioss.api.OssApi;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class OSSTest {
String endpoint = "改为自己的";
String accessKeyId = "改为自己的";
String accessKeySecret = "改为自己的";
String newBucketName = "oneblog-bucket";
@Test
public void createBucket() {
OssApi ossApi = new OssApi(endpoint, accessKeyId, accessKeySecret);
ossApi.createBucket(newBucketName);
}
@Test
public void uploadFile() {
String filePath = "D:\\zhangyadong\\zyd-project\\文件\\公众号素材\\timg.jpg";
OssApi ossApi = new OssApi(endpoint, accessKeyId, accessKeySecret);
try {
InputStream inputStream = new FileInputStream(filePath);
String res = ossApi.uploadFile(inputStream, "test/asd/111111.png", newBucketName);
System.out.println(res);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
@Test
public void deleteFile() {
OssApi ossApi = new OssApi(endpoint, accessKeyId, accessKeySecret);
ossApi.deleteFile("test/asd/111111.png", newBucketName);
}
}
/**
* MIT License
*
* Copyright (c) 2018 yadong.zhang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @website https://www.zhyd.me
......
/**
* MIT License
*
* Copyright (c) 2018 yadong.zhang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @website https://www.zhyd.me
......
/**
* MIT License
*
* Copyright (c) 2018 yadong.zhang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
* 评论插件(md版)
*
......
/**
* MIT License
*
* Copyright (c) 2018 yadong.zhang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* 项目核心Js库,主要包含核心工具类和 相关插件
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
......@@ -205,38 +183,63 @@
$.extend({
alert: {
info: function (content, delayTime, callback) {
info: function (content, callback, delayTime) {
delayTime = delayTime ? "confirm|" + delayTime : "confirm|3000";
$.jqAlert({
icon: 'fa fa-info-circle',
title: '友情提示',
content: content,
confirmButton: '关闭',
type: 'green',
typeAnimated: true,
autoClose: delayTime,
confirm: callback
buttons: {
confirm: {
text: "关闭",
btnClass: 'btn-default',
action: callback
}
}
});
},
error: function (content, delayTime, callback) {
error: function (content, callback, delayTime) {
delayTime = delayTime ? "confirm|" + delayTime : "confirm|3000";
$.jqAlert({
icon: 'fa fa-exclamation-circle',
title: '警告',
content: content,
confirmButton: '关闭',
autoClose: delayTime,
confirm: callback
type: 'orange',
typeAnimated: true,
buttons: {
confirm: {
text: "关闭",
btnClass: 'btn-default',
action: callback
}
}
});
},
confirm: function (content, confirmCallback, cancelCallback, delayTime) {
delayTime = delayTime ? "cancel|" + delayTime : "cancel|5000";
$.jqConfirm({
confirmButtonClass: 'btn-success',
cancelButtonClass: 'btn-default',
title: '友情提示',
icon: 'fa fa-question-circle',
title: '确认?',
content: content,
autoClose: delayTime,
confirmButton: '确定',
cancelButton: '关闭',
confirm: confirmCallback,
cancel: cancelCallback
type: 'dark',
typeAnimated: true,
buttons: {
confirm: {
text: '确定',
btnClass: 'btn-green',
action: confirmCallback
},
cancel: {
text: '取消',
btnClass: 'btn-default',
action: cancelCallback
}
}
});
},
ajaxSuccessConfirm: function (json, callback) {
......
/**
* MIT License
*
* Copyright (c) 2018 yadong.zhang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @website https://www.zhyd.me
......
......@@ -65,7 +65,7 @@
<h4>${title}</h4>
<p class="blog-description" id="hitokoto"></p>
<div>
<a href="javascript:void(0);" target="_blank" title="点击QQ联系我"onclick="window.open('tencent://message/?uin=${config.qq}&amp;Site=www.${config.domain}&amp;Menu=yes')" rel="external nofollow"><i class="fa fa fa-qq fa-fw"></i>QQ联系</a>
<a href="javascript:void(0);" target="_blank" title="点击QQ联系我" onclick="window.open('tencent://message/?uin=${config.qq}&amp;Site=www.${config.domain}&amp;Menu=yes')" rel="external nofollow"><i class="fa fa fa-qq fa-fw"></i>QQ联系</a>
|
<a href="mailto:${config.authorEmail}" target="_blank" title="点击给我发邮件" rel="external nofollow"><i class="fa fa fa-envelope fa-fw"></i>邮箱联系</a>
|
......@@ -78,10 +78,10 @@
<#-- 页面顶部菜单下方提示栏 -->
<#macro prompt>
<!--[if lt IE 9]><div class="alert alert-danger topframe" role="alert">Oh My God!你的浏览器实在<strong>太太太太太太旧了</strong>,赶紧升级浏览器 <a target="_blank" class="alert-link" href="http://browsehappy.com">立即升级</a></div><![endif]-->
<#if config.maintenance?if_exists && config.maintenance>
<#if config.maintenance?if_exists && config.maintenance == 1>
<div class="alert alert-warning fade-in" role="alert">
<a href="#" class="close" data-dismiss="alert">&times;</a>
系统预计将在<strong>${config.maintenanceDate}</strong>进行更新,届时网站将无法使用,更新时间大约 5-10分钟,敬请悉知。
系统预计将在<strong>${config.maintenanceDate}</strong>左右进行更新维护,届时网站将无法使用,更新大约持续${config.maintenanceTime!(30)}分钟,敬请悉知。
</div>
</#if>
</#macro>
......
......@@ -145,7 +145,7 @@
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery_lazyload/1.9.7/jquery.lazyload.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery-confirm/2.5.1/jquery-confirm.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery-confirm/3.3.2/jquery-confirm.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/fancybox/2.1.5/jquery.fancybox.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery.bootstrapvalidator/0.5.1/js/bootstrapValidator.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/js-xss/0.3.3/xss.min.js"></script>
......
<link href="${config.siteFavicon}" rel="shortcut icon" type="image/x-icon">
<link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="https://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/jquery-confirm/2.5.1/jquery-confirm.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/jquery-confirm/3.3.2/jquery-confirm.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/fancybox/2.1.5/jquery.fancybox.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/jquery.bootstrapvalidator/0.5.1/css/bootstrapValidator.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
......
......@@ -48,6 +48,8 @@
<useragentutils.version>1.20</useragentutils.version>
<braum.version>1.0.0-alpha</braum.version>
<hutool.version>4.1.21</hutool.version>
<spring.web.version>5.1.4.RELEASE</spring.web.version>
<aliyun.oss.version>2.8.3</aliyun.oss.version>
</properties>
<dependencyManagement>
......
......@@ -3,14 +3,16 @@
----
### 2019-02-12
### 2019-02-12~2019-02-13
- 新增
- 文件资源库,项目中所有上传的文件统一保存到file表中,方便复用
- 文件资源库,项目中所有上传的文件统一保存到file表中
- 项目中的文件存储默认为本地存储,需要按照例子自己配置本地文件服务器
- 文件云存储支持阿里云OSS
- 修改
- sys_config相关的逻辑,删除BaseConfig类
- jquery-confirm插件升级:v2.5.1 -> v3.3.2
- 后台评论管理列表样式修改
- 其他
- 删除一些无用的文件
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册