mirror of https://github.com/halo-dev/halo
🍎 新增备份功能(资源文件,数据库,文章)
parent
2bd40d3ec8
commit
18a60f07f1
|
@ -31,5 +31,7 @@ nbdist/
|
|||
*/.DS_Store
|
||||
|
||||
### VS Code ###
|
||||
.project
|
||||
.factorypath
|
||||
*.project
|
||||
*.factorypath
|
||||
|
||||
~/
|
||||
|
|
|
@ -38,6 +38,7 @@ public class MvcConfig implements WebMvcConfigurer {
|
|||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(loginInterceptor)
|
||||
.addPathPatterns("/admin/**")
|
||||
.addPathPatterns("/backup/**")
|
||||
.excludePathPatterns("/admin/login")
|
||||
.excludePathPatterns("/admin/getLogin")
|
||||
.excludePathPatterns("/static/**");
|
||||
|
@ -63,5 +64,7 @@ public class MvcConfig implements WebMvcConfigurer {
|
|||
.addResourceLocations("classpath:/upload/");
|
||||
registry.addResourceHandler("/favicon.ico")
|
||||
.addResourceLocations("classpath:/static/images/favicon.ico");
|
||||
registry.addResourceHandler("/backup/**")
|
||||
.addResourceLocations("file:///"+System.getProperties().getProperty("user.home")+"/halo/backup/");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ public class User implements Serializable {
|
|||
/**
|
||||
* 是否禁用登录
|
||||
*/
|
||||
private String loginEnable;
|
||||
private String loginEnable = "true";
|
||||
|
||||
/**
|
||||
* 最后一次登录时间
|
||||
|
@ -71,5 +71,5 @@ public class User implements Serializable {
|
|||
/**
|
||||
* 登录错误次数记录
|
||||
*/
|
||||
private Integer loginError;
|
||||
private Integer loginError = 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package cc.ryanc.halo.model.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author : RYAN0UP
|
||||
* @version : 1.0
|
||||
* @date : 2018/6/4
|
||||
*/
|
||||
@Data
|
||||
public class BackupDto {
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createAt;
|
||||
|
||||
/**
|
||||
* 文件大小
|
||||
*/
|
||||
private String fileSize;
|
||||
|
||||
/**
|
||||
* 文件类型
|
||||
*/
|
||||
private String fileType;
|
||||
|
||||
/**
|
||||
* 备份类型
|
||||
*/
|
||||
private String backupType;
|
||||
}
|
|
@ -27,4 +27,15 @@ public interface MailService {
|
|||
* @param templateName 模板路径
|
||||
*/
|
||||
void sendTemplateMail(String to, String subject, Map<String, Object> content, String templateName);
|
||||
|
||||
/**
|
||||
* 发送带有附件的邮件
|
||||
*
|
||||
* @param to 接收者
|
||||
* @param subject 主题
|
||||
* @param content 内容
|
||||
* @param templateName 模板路径
|
||||
* @param attachSrc 附件路径
|
||||
*/
|
||||
void sendAttachMail(String to, String subject, Map<String, Object> content, String templateName, String attachSrc);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
|
||||
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -76,4 +77,36 @@ public class MailServiceImpl implements MailService {
|
|||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带有附件的邮件
|
||||
*
|
||||
* @param to 接收者
|
||||
* @param subject 主题
|
||||
* @param content 内容
|
||||
* @param templateName 模板路径
|
||||
* @param attachSrc 附件路径
|
||||
*/
|
||||
@Override
|
||||
public void sendAttachMail(String to, String subject, Map<String, Object> content, String templateName, String attachSrc) {
|
||||
//配置邮件服务器
|
||||
HaloUtils.configMail(
|
||||
HaloConst.OPTIONS.get("mail_smtp_host"),
|
||||
HaloConst.OPTIONS.get("mail_smtp_username"),
|
||||
HaloConst.OPTIONS.get("mail_smtp_password"));
|
||||
File file = new File(attachSrc);
|
||||
String text = "";
|
||||
try{
|
||||
Template template = freeMarker.getConfiguration().getTemplate(templateName);
|
||||
text = FreeMarkerTemplateUtils.processTemplateIntoString(template,content);
|
||||
OhMyEmail.subject(subject)
|
||||
.from(HaloConst.OPTIONS.get("mail_from_name"))
|
||||
.to(to)
|
||||
.html(text)
|
||||
.attach(file,file.getName())
|
||||
.send();
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package cc.ryanc.halo.utils;
|
||||
|
||||
import cc.ryanc.halo.model.domain.Post;
|
||||
import cc.ryanc.halo.model.dto.BackupDto;
|
||||
import cc.ryanc.halo.model.dto.HaloConst;
|
||||
import cc.ryanc.halo.model.dto.Theme;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.sun.syndication.feed.rss.Channel;
|
||||
import com.sun.syndication.feed.rss.Content;
|
||||
import com.sun.syndication.feed.rss.Item;
|
||||
|
@ -17,13 +19,18 @@ import javax.imageio.ImageIO;
|
|||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
|
@ -104,6 +111,84 @@ public class HaloUtils {
|
|||
return FILE_LIST;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取备份文件信息
|
||||
*
|
||||
* @param dir dir
|
||||
* @return List<BackupDto></>
|
||||
*/
|
||||
public static List<BackupDto> getBackUps(String dir) {
|
||||
String srcPathStr = System.getProperties().getProperty("user.home") + "/halo/backup/" + dir;
|
||||
File srcPath = new File(srcPathStr);
|
||||
File[] files = srcPath.listFiles();
|
||||
List<BackupDto> backupDtos = new ArrayList<>();
|
||||
BackupDto backupDto = null;
|
||||
//遍历文件
|
||||
for (File file : files) {
|
||||
if (file.isFile()) {
|
||||
if (StringUtils.equals(file.getName(), ".DS_Store")) {
|
||||
continue;
|
||||
}
|
||||
backupDto = new BackupDto();
|
||||
backupDto.setFileName(file.getName());
|
||||
backupDto.setCreateAt(getCreateTime(file.getAbsolutePath()));
|
||||
backupDto.setFileType(FileUtil.getType(file));
|
||||
backupDto.setFileSize(parseSize(file.length()));
|
||||
backupDto.setBackupType(dir);
|
||||
backupDtos.add(backupDto);
|
||||
}
|
||||
}
|
||||
return backupDtos;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换文件大小
|
||||
*
|
||||
* @param size size
|
||||
* @return string
|
||||
*/
|
||||
public static String parseSize(long size) {
|
||||
if (size < 1024) {
|
||||
return String.valueOf(size) + "B";
|
||||
} else {
|
||||
size = size / 1024;
|
||||
}
|
||||
if (size < 1024) {
|
||||
return String.valueOf(size) + "KB";
|
||||
} else {
|
||||
size = size / 1024;
|
||||
}
|
||||
if (size < 1024) {
|
||||
size = size * 100;
|
||||
return String.valueOf((size / 100)) + "." + String.valueOf((size % 100)) + "MB";
|
||||
} else {
|
||||
size = size * 100 / 1024;
|
||||
return String.valueOf((size / 100)) + "." + String.valueOf((size % 100)) + "GB";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件创建时间
|
||||
*
|
||||
* @param srcPath 文件绝对路径
|
||||
* @return 时间
|
||||
*/
|
||||
public static Date getCreateTime(String srcPath) {
|
||||
Path path = Paths.get(srcPath);
|
||||
BasicFileAttributeView basicview = Files.getFileAttributeView(path, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
|
||||
BasicFileAttributes attr;
|
||||
try {
|
||||
attr = basicview.readAttributes();
|
||||
Date createDate = new Date(attr.creationTime().toMillis());
|
||||
return createDate;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(1970, 0, 1, 0, 0, 0);
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有主题
|
||||
*
|
||||
|
@ -215,26 +300,6 @@ public class HaloUtils {
|
|||
return Instant.ofEpochSecond(unixTime).atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern(format));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端ip地址
|
||||
*
|
||||
* @param request request
|
||||
* @return string
|
||||
*/
|
||||
public static String getIpAddr(HttpServletRequest request) {
|
||||
String ip = request.getHeader("x-forwarded-for");
|
||||
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份数据库
|
||||
*
|
||||
|
@ -288,7 +353,14 @@ public class HaloUtils {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static void dbToFile(String data,String filePath,String fileName){
|
||||
/**
|
||||
* 导出为文件
|
||||
*
|
||||
* @param data 内容
|
||||
* @param filePath 保存路径
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
public static void postToFile(String data, String filePath, String fileName) {
|
||||
try{
|
||||
File file =new File(filePath);
|
||||
if(!file.exists()){
|
||||
|
@ -299,7 +371,6 @@ public class HaloUtils {
|
|||
BufferedWriter bufferWritter = new BufferedWriter(fileWritter);
|
||||
bufferWritter.write(data);
|
||||
bufferWritter.close();
|
||||
System.out.println("Done");
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import cn.hutool.core.date.DateUtil;
|
|||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -126,9 +127,11 @@ public class AdminController extends BaseController {
|
|||
//已注册账号,单用户,只有一个
|
||||
User aUser = userService.findUser();
|
||||
//首先判断是否已经被禁用已经是否已经过了10分钟
|
||||
Date loginLast = aUser.getLoginLast();
|
||||
Date loginLast = DateUtil.date();
|
||||
if(null!=aUser.getLoginLast()){
|
||||
loginLast = aUser.getLoginLast();
|
||||
}
|
||||
Long between = DateUtil.between(loginLast, DateUtil.date(), DateUnit.MINUTE);
|
||||
log.info(between+"");
|
||||
if (StringUtils.equals(aUser.getLoginEnable(), "false") && (between < 10)) {
|
||||
return new JsonResult(0, "已禁止登录,请10分钟后再试");
|
||||
}
|
||||
|
@ -145,7 +148,7 @@ public class AdminController extends BaseController {
|
|||
session.setAttribute(HaloConst.USER_SESSION_KEY, aUser);
|
||||
//重置用户的登录状态为正常
|
||||
userService.updateUserNormal();
|
||||
logsService.saveByLogs(new Logs(LogsRecord.LOGIN, LogsRecord.LOGIN_SUCCESS, HaloUtils.getIpAddr(request), DateUtil.date()));
|
||||
logsService.saveByLogs(new Logs(LogsRecord.LOGIN, LogsRecord.LOGIN_SUCCESS, ServletUtil.getClientIP(request), DateUtil.date()));
|
||||
return new JsonResult(1, "登录成功!");
|
||||
} else {
|
||||
//更新失败次数
|
||||
|
@ -158,7 +161,7 @@ public class AdminController extends BaseController {
|
|||
new Logs(
|
||||
LogsRecord.LOGIN,
|
||||
LogsRecord.LOGIN_ERROR + "[" + HtmlUtil.encode(loginName) + "," + HtmlUtil.encode(loginPwd) + "]",
|
||||
HaloUtils.getIpAddr(request),
|
||||
ServletUtil.getClientIP(request),
|
||||
DateUtil.date()
|
||||
)
|
||||
);
|
||||
|
@ -175,7 +178,7 @@ public class AdminController extends BaseController {
|
|||
@GetMapping(value = "/logOut")
|
||||
public String logOut(HttpSession session) {
|
||||
User user = (User) session.getAttribute(HaloConst.USER_SESSION_KEY);
|
||||
logsService.saveByLogs(new Logs(LogsRecord.LOGOUT, user.getUserName(), HaloUtils.getIpAddr(request), DateUtil.date()));
|
||||
logsService.saveByLogs(new Logs(LogsRecord.LOGOUT, user.getUserName(), ServletUtil.getClientIP(request), DateUtil.date()));
|
||||
session.invalidate();
|
||||
log.info("用户[" + user.getUserName() + "]退出登录");
|
||||
return "redirect:/admin/login";
|
||||
|
|
|
@ -9,6 +9,7 @@ import cc.ryanc.halo.service.AttachmentService;
|
|||
import cc.ryanc.halo.service.LogsService;
|
||||
import cc.ryanc.halo.utils.HaloUtils;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -174,7 +175,7 @@ public class AttachmentController {
|
|||
updateConst();
|
||||
log.info("上传文件[" + fileName + "]到[" + mediaPath.getAbsolutePath() + "]成功");
|
||||
logsService.saveByLogs(
|
||||
new Logs(LogsRecord.UPLOAD_FILE, fileName, HaloUtils.getIpAddr(request), DateUtil.date())
|
||||
new Logs(LogsRecord.UPLOAD_FILE, fileName, ServletUtil.getClientIP(request), DateUtil.date())
|
||||
);
|
||||
|
||||
result.put("success", 1);
|
||||
|
@ -246,7 +247,7 @@ public class AttachmentController {
|
|||
updateConst();
|
||||
log.info("删除文件[" + delFileName + "]成功!");
|
||||
logsService.saveByLogs(
|
||||
new Logs(LogsRecord.REMOVE_FILE, delFileName, HaloUtils.getIpAddr(request), DateUtil.date())
|
||||
new Logs(LogsRecord.REMOVE_FILE, delFileName, ServletUtil.getClientIP(request), DateUtil.date())
|
||||
);
|
||||
} else {
|
||||
log.error("删除附件[" + delFileName + "]失败!");
|
||||
|
|
|
@ -1,18 +1,31 @@
|
|||
package cc.ryanc.halo.web.controller.admin;
|
||||
|
||||
import cc.ryanc.halo.model.domain.Post;
|
||||
import cc.ryanc.halo.model.domain.User;
|
||||
import cc.ryanc.halo.model.dto.BackupDto;
|
||||
import cc.ryanc.halo.model.dto.HaloConst;
|
||||
import cc.ryanc.halo.model.dto.JsonResult;
|
||||
import cc.ryanc.halo.service.MailService;
|
||||
import cc.ryanc.halo.service.PostService;
|
||||
import cc.ryanc.halo.utils.HaloUtils;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author : RYAN0UP
|
||||
|
@ -28,6 +41,10 @@ public class BackupController {
|
|||
@Autowired
|
||||
private PostService postService;
|
||||
|
||||
@Autowired
|
||||
private MailService mailService;
|
||||
|
||||
|
||||
/**
|
||||
* 渲染备份页面
|
||||
*
|
||||
|
@ -35,55 +52,162 @@ public class BackupController {
|
|||
* @return 模板路径admin/admin_backup
|
||||
*/
|
||||
@GetMapping
|
||||
public String backup() {
|
||||
public String backup(Model model) {
|
||||
List<BackupDto> resourcesBackup = HaloUtils.getBackUps("resources");
|
||||
List<BackupDto> databasesBackup = HaloUtils.getBackUps("databases");
|
||||
List<BackupDto> postsBackup = HaloUtils.getBackUps("posts");
|
||||
model.addAttribute("resourcesBackup", resourcesBackup);
|
||||
model.addAttribute("databasesBackup", databasesBackup);
|
||||
model.addAttribute("postsBackup", postsBackup);
|
||||
return "admin/admin_backup";
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行备份
|
||||
*
|
||||
* @param type 备份类型
|
||||
* @return JsonResult
|
||||
*/
|
||||
@GetMapping(value = "doBackup")
|
||||
@ResponseBody
|
||||
public JsonResult doBackup(@RequestParam("type") String type) {
|
||||
if (StringUtils.equals("resources", type)) {
|
||||
return this.backupResources();
|
||||
} else if (StringUtils.equals("db", type)) {
|
||||
return this.backupDatabase();
|
||||
} else if (StringUtils.equals("posts", type)) {
|
||||
return this.backupPosts();
|
||||
} else {
|
||||
return new JsonResult(0, "备份失败!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份数据库
|
||||
*
|
||||
* @return 重定向到/admin/backup
|
||||
*/
|
||||
@GetMapping(value = "/backupDb")
|
||||
public String backupDatabase() {
|
||||
String fileName = "db_backup_" + HaloUtils.getStringDate("yyyy_MM_dd_HH_mm_ss") + ".sql";
|
||||
public JsonResult backupDatabase() {
|
||||
try {
|
||||
File path = new File(ResourceUtils.getURL("classpath:").getPath());
|
||||
String savePath = path.getAbsolutePath() + "/backup/database";
|
||||
HaloUtils.exportDatabase("localhost", "root", "123456", savePath, fileName, "testdb");
|
||||
String srcPath = System.getProperties().getProperty("user.home") + "/halo";
|
||||
String distName = "databases_backup_" + HaloUtils.getStringDate("yyyyMMddHHmmss");
|
||||
ZipUtil.zip(srcPath + "/halo.mv.db", System.getProperties().getProperty("user.home") + "/halo/backup/databases/" + distName + ".zip");
|
||||
return new JsonResult(1, "备份成功!");
|
||||
} catch (Exception e) {
|
||||
log.error("未知错误:{0}", e.getMessage());
|
||||
log.error("未知错误:", e.getMessage());
|
||||
return new JsonResult(0, "备份失败!");
|
||||
}
|
||||
return "redirect:/admin/backup";
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份资源文件 重要
|
||||
*
|
||||
* @return return
|
||||
* @return JsonResult
|
||||
*/
|
||||
@GetMapping(value = "/backupRe")
|
||||
public String backupResources() {
|
||||
return null;
|
||||
public JsonResult backupResources() {
|
||||
try {
|
||||
File path = new File(ResourceUtils.getURL("classpath:").getPath());
|
||||
String srcPath = path.getAbsolutePath();
|
||||
String distName = "resources_backup_" + HaloUtils.getStringDate("yyyyMMddHHmmss");
|
||||
//执行打包
|
||||
ZipUtil.zip(srcPath, System.getProperties().getProperty("user.home") + "/halo/backup/resources/" + distName + ".zip");
|
||||
return new JsonResult(1, "备份成功!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new JsonResult(0, "备份失败!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文章,导出markdown文件
|
||||
*
|
||||
* @return 重定向到/admin/backup
|
||||
* @return JsonResult
|
||||
*/
|
||||
@GetMapping(value = "/backupPost")
|
||||
public String backupPosts() {
|
||||
public JsonResult backupPosts() {
|
||||
List<Post> posts = postService.findAllPosts(HaloConst.POST_TYPE_POST);
|
||||
posts.addAll(postService.findAllPosts(HaloConst.POST_TYPE_PAGE));
|
||||
try {
|
||||
File path = new File(ResourceUtils.getURL("classpath:").getPath());
|
||||
String savePath = path.getAbsolutePath() + "/backup/posts/posts_backup_" + HaloUtils.getStringDate("yyyy_MM_dd_HH_mm_ss");
|
||||
//打包好的文件名
|
||||
String distName = "posts_backup_" + HaloUtils.getStringDate("yyyyMMddHHmmss");
|
||||
String srcPath = System.getProperties().getProperty("user.home") + "/halo/backup/posts/" + distName;
|
||||
for (Post post : posts) {
|
||||
HaloUtils.dbToFile(post.getPostContentMd(), savePath, post.getPostTitle() + ".md");
|
||||
HaloUtils.postToFile(post.getPostContentMd(), srcPath, post.getPostTitle() + ".md");
|
||||
}
|
||||
//打包导出好的文章
|
||||
ZipUtil.zip(srcPath, System.getProperties().getProperty("user.home") + "/halo/backup/posts/" + distName + ".zip");
|
||||
FileUtil.del(srcPath);
|
||||
return new JsonResult(1, "备份成功!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new JsonResult(0, "备份失败!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除备份
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @param type 备份类型
|
||||
* @return JsonResult
|
||||
*/
|
||||
@GetMapping(value = "delBackup")
|
||||
@ResponseBody
|
||||
public JsonResult delBackup(@RequestParam("fileName") String fileName,
|
||||
@RequestParam("type") String type) {
|
||||
String srcPath = System.getProperties().getProperty("user.home") + "/halo/backup/" + type + "/" + fileName;
|
||||
try {
|
||||
FileUtil.del(srcPath);
|
||||
return new JsonResult(1, "删除成功!");
|
||||
} catch (Exception e) {
|
||||
return new JsonResult(0, "删除失败!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将备份发送到邮箱
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @param type 备份类型
|
||||
* @return JsonResult
|
||||
*/
|
||||
@GetMapping(value = "sendToEmail")
|
||||
@ResponseBody
|
||||
public JsonResult sendToEmail(@RequestParam("fileName") String fileName,
|
||||
@RequestParam("type") String type,
|
||||
HttpSession session) {
|
||||
String srcPath = System.getProperties().getProperty("user.home") + "/halo/backup/" + type + "/" + fileName;
|
||||
User user = (User) session.getAttribute(HaloConst.USER_SESSION_KEY);
|
||||
if (null == user.getUserEmail() || StringUtils.equals(user.getUserEmail(), "")) {
|
||||
return new JsonResult(0, "博主邮箱没有配置!");
|
||||
}
|
||||
if (StringUtils.equals(HaloConst.OPTIONS.get("smtp_email_enable"), "false")) {
|
||||
return new JsonResult(0, "发信邮箱没有配置!");
|
||||
}
|
||||
new EmailToAdmin(srcPath, user).start();
|
||||
return new JsonResult(1, "邮件发送成功!");
|
||||
}
|
||||
|
||||
class EmailToAdmin extends Thread {
|
||||
private String srcPath;
|
||||
private User user;
|
||||
|
||||
public EmailToAdmin(String srcPath, User user) {
|
||||
this.srcPath = srcPath;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
File file = new File(srcPath);
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
try {
|
||||
content.put("fileName", file.getName());
|
||||
content.put("createAt", HaloUtils.getCreateTime(srcPath));
|
||||
content.put("size", HaloUtils.parseSize(file.length()));
|
||||
mailService.sendAttachMail(user.getUserEmail(), "有新的备份!", content, "common/mail/mail_attach.ftl", srcPath);
|
||||
} catch (Exception e) {
|
||||
log.error("邮件服务器未配置:", e.getMessage());
|
||||
}
|
||||
}
|
||||
return "redirect:/admin/backup";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import cc.ryanc.halo.web.controller.core.BaseController;
|
|||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -195,7 +196,7 @@ public class CommentController extends BaseController{
|
|||
comment.setCommentAuthor(user.getUserDisplayName());
|
||||
comment.setCommentAuthorEmail(user.getUserEmail());
|
||||
comment.setCommentAuthorUrl(HaloConst.OPTIONS.get("blog_url"));
|
||||
comment.setCommentAuthorIp(HaloUtils.getIpAddr(request));
|
||||
comment.setCommentAuthorIp(ServletUtil.getClientIP(request));
|
||||
comment.setCommentAuthorAvatarMd5(SecureUtil.md5(user.getUserEmail()));
|
||||
comment.setCommentDate(DateUtil.date());
|
||||
String lastContent = " //<a href='#comment-id-"+lastComment.getCommentId()+"'>@"+lastComment.getCommentAuthor()+"</a>:"+lastComment.getCommentContent();
|
||||
|
|
|
@ -10,6 +10,7 @@ import cc.ryanc.halo.service.LogsService;
|
|||
import cc.ryanc.halo.service.PostService;
|
||||
import cc.ryanc.halo.utils.HaloUtils;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -232,7 +233,7 @@ public class PageController {
|
|||
post.setPostUpdate(DateUtil.date());
|
||||
}
|
||||
postService.saveByPost(post);
|
||||
logsService.saveByLogs(new Logs(LogsRecord.PUSH_PAGE, post.getPostTitle(), HaloUtils.getIpAddr(request), DateUtil.date()));
|
||||
logsService.saveByLogs(new Logs(LogsRecord.PUSH_PAGE, post.getPostTitle(), ServletUtil.getClientIP(request), DateUtil.date()));
|
||||
return new JsonResult(1,msg);
|
||||
} catch (Exception e) {
|
||||
log.error("未知错误:{0}", e.getMessage());
|
||||
|
|
|
@ -11,6 +11,7 @@ import cc.ryanc.halo.service.TagService;
|
|||
import cc.ryanc.halo.utils.HaloUtils;
|
||||
import cc.ryanc.halo.web.controller.core.BaseController;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -193,7 +194,7 @@ public class PostController extends BaseController{
|
|||
}
|
||||
post.setPostUrl(urlFilter(post.getPostUrl()));
|
||||
postService.saveByPost(post);
|
||||
logsService.saveByLogs(new Logs(LogsRecord.PUSH_POST,post.getPostTitle(),HaloUtils.getIpAddr(request),DateUtil.date()));
|
||||
logsService.saveByLogs(new Logs(LogsRecord.PUSH_POST,post.getPostTitle(),ServletUtil.getClientIP(request),DateUtil.date()));
|
||||
return new JsonResult(1,msg);
|
||||
}catch (Exception e){
|
||||
log.error("未知错误:", e.getMessage());
|
||||
|
@ -298,7 +299,7 @@ public class PostController extends BaseController{
|
|||
try{
|
||||
Optional<Post> post = postService.findByPostId(postId);
|
||||
postService.removeByPostId(postId);
|
||||
logsService.saveByLogs(new Logs(LogsRecord.REMOVE_POST,post.get().getPostTitle(),HaloUtils.getIpAddr(request),DateUtil.date()));
|
||||
logsService.saveByLogs(new Logs(LogsRecord.REMOVE_POST,post.get().getPostTitle(),ServletUtil.getClientIP(request),DateUtil.date()));
|
||||
}catch (Exception e){
|
||||
log.error("未知错误:{0}",e.getMessage());
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import cc.ryanc.halo.web.controller.core.BaseController;
|
|||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -78,7 +79,7 @@ public class ThemeController extends BaseController {
|
|||
BaseController.THEME = siteTheme;
|
||||
log.info("已将主题改变为:" + siteTheme);
|
||||
logsService.saveByLogs(
|
||||
new Logs(LogsRecord.CHANGE_THEME, "更换为" + siteTheme, HaloUtils.getIpAddr(request), DateUtil.date())
|
||||
new Logs(LogsRecord.CHANGE_THEME, "更换为" + siteTheme, ServletUtil.getClientIP(request), DateUtil.date())
|
||||
);
|
||||
return new JsonResult(1,"主题已设置为"+siteTheme);
|
||||
} catch (Exception e) {
|
||||
|
@ -106,7 +107,7 @@ public class ThemeController extends BaseController {
|
|||
file.transferTo(themePath);
|
||||
log.info("上传主题成功,路径:" + themePath.getAbsolutePath());
|
||||
logsService.saveByLogs(
|
||||
new Logs(LogsRecord.UPLOAD_THEME, file.getOriginalFilename(), HaloUtils.getIpAddr(request), DateUtil.date())
|
||||
new Logs(LogsRecord.UPLOAD_THEME, file.getOriginalFilename(), ServletUtil.getClientIP(request), DateUtil.date())
|
||||
);
|
||||
ZipUtil.unzip(themePath,new File(basePath.getAbsolutePath(), "templates/themes/"));
|
||||
FileUtil.del(themePath);
|
||||
|
|
|
@ -7,6 +7,7 @@ import cc.ryanc.halo.service.*;
|
|||
import cc.ryanc.halo.utils.HaloUtils;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import freemarker.template.Configuration;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -177,7 +178,7 @@ public class InstallController {
|
|||
new Logs(
|
||||
LogsRecord.INSTALL,
|
||||
"安装成功,欢迎使用Halo。",
|
||||
HaloUtils.getIpAddr(request),
|
||||
ServletUtil.getClientIP(request),
|
||||
DateUtil.date()
|
||||
)
|
||||
);
|
||||
|
|
|
@ -11,6 +11,7 @@ import cc.ryanc.halo.service.UserService;
|
|||
import cc.ryanc.halo.utils.HaloUtils;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -103,7 +104,7 @@ public class FrontCommentController {
|
|||
comment.setCommentAuthorEmail(HtmlUtil.encode(comment.getCommentAuthorEmail()).toLowerCase());
|
||||
comment.setPost(post);
|
||||
comment.setCommentDate(DateUtil.date());
|
||||
comment.setCommentAuthorIp(HaloUtils.getIpAddr(request));
|
||||
comment.setCommentAuthorIp(ServletUtil.getClientIP(request));
|
||||
comment.setIsAdmin(0);
|
||||
comment.setCommentAuthor(HtmlUtil.encode(comment.getCommentAuthor()));
|
||||
if(comment.getCommentParent()>0){
|
||||
|
|
|
@ -7,21 +7,310 @@
|
|||
<#include "module/_sidebar.ftl">
|
||||
<div class="content-wrapper">
|
||||
<section class="content-header">
|
||||
<h1 style="display: inline-block;">正在开发中...</h1>
|
||||
<h1 style="display: inline-block;">博客备份</h1>
|
||||
<ol class="breadcrumb">
|
||||
<li>
|
||||
<a href="/admin"><i class="fa fa-dashboard"></i> 首页</a>
|
||||
</li>
|
||||
<li><a href="#">设置</a></li>
|
||||
<li class="active">备份恢复</li>
|
||||
<li class="active">博客备份</li>
|
||||
</ol>
|
||||
</section>
|
||||
<!--
|
||||
<section class="content container-fluid">
|
||||
<a href="/admin/backup/backupDb">备份数据库</a>
|
||||
<a href="/admin/backup/backupPost">备份文章</a>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="nav-tabs-custom">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="#resources" data-toggle="tab">资源目录备份</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#database" data-toggle="tab">数据库备份</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#post" data-toggle="tab">文章备份</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="resources">
|
||||
<form method="post" class="form-horizontal" id="resourcesBackup">
|
||||
<div class="box-body table-responsive" style="padding: 10px 0;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文件名称</th>
|
||||
<th>备份时间</th>
|
||||
<th>文件大小</th>
|
||||
<th>文件类型</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#if resourcesBackup?size gt 0>
|
||||
<#list resourcesBackup as resource>
|
||||
<tr>
|
||||
<td>${resource.fileName}</td>
|
||||
<td>${resource.createAt?string("yyyy-MM-dd HH:mm")}</td>
|
||||
<td>${resource.fileSize}</td>
|
||||
<td>${resource.fileType}</td>
|
||||
<td>
|
||||
<a href="/backup/resources/${resource.fileName}" class="btn btn-xs btn-primary" download="${resource.fileName}">下载</a>
|
||||
<button type="button" class="btn btn-xs btn-info" onclick="sendToEmail('${resource.fileName}','${resource.backupType}')">发送到邮箱</button>
|
||||
<button type="button" class="btn btn-xs btn-danger" onclick="delBackup('${resource.fileName}','${resource.backupType}')">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
<#else>
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center">暂无备份</td>
|
||||
</tr>
|
||||
</#if>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm " onclick="btn_backup('resources')">备份</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="database">
|
||||
<form method="post" class="form-horizontal" id="databaseBackup">
|
||||
<div class="box-body table-responsive" style="padding: 10px 0;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文件名称</th>
|
||||
<th>备份时间</th>
|
||||
<th>文件大小</th>
|
||||
<th>文件类型</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#if databasesBackup?size gt 0>
|
||||
<#list databasesBackup as database>
|
||||
<tr>
|
||||
<td>${database.fileName}</td>
|
||||
<td>${database.createAt?string("yyyy-MM-dd HH:mm")}</td>
|
||||
<td>${database.fileSize}</td>
|
||||
<td>${database.fileType}</td>
|
||||
<td>
|
||||
<a href="/backup/databases/${database.fileName}" class="btn btn-xs btn-primary" download="${database.fileName}">下载</a>
|
||||
<button type="button" class="btn btn-xs btn-info" onclick="sendToEmail('${database.fileName}','${database.backupType}')">发送到邮箱</button>
|
||||
<button type="button" class="btn btn-xs btn-danger" onclick="delBackup('${database.fileName}','${database.backupType}')">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
<#else>
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center">暂无备份</td>
|
||||
</tr>
|
||||
</#if>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm " onclick="btn_backup('db')">备份</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane" id="post">
|
||||
<form method="post" class="form-horizontal" id="postBackup">
|
||||
<div class="box-body table-responsive" style="padding: 10px 0;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文件名称</th>
|
||||
<th>备份时间</th>
|
||||
<th>文件大小</th>
|
||||
<th>文件类型</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#if postsBackup?size gt 0>
|
||||
<#list postsBackup as post>
|
||||
<tr>
|
||||
<td>${post.fileName}</td>
|
||||
<td>${post.createAt?string("yyyy-MM-dd HH:mm")}</td>
|
||||
<td>${post.fileSize}</td>
|
||||
<td>${post.fileType}</td>
|
||||
<td>
|
||||
<a href="/backup/posts/${post.fileName}" class="btn btn-xs btn-primary" download="${post.fileName}">下载</a>
|
||||
<button type="button" class="btn btn-xs btn-info" onclick="sendToEmail('${post.fileName}','${post.backupType}')">发送到邮箱</button>
|
||||
<button type="button" class="btn btn-xs btn-danger" onclick="delBackup('${post.fileName}','${post.backupType}')">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</#list>
|
||||
<#else>
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center">暂无备份</td>
|
||||
</tr>
|
||||
</#if>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="button" class="btn btn-primary btn-sm " onclick="btn_backup('posts')">备份</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
-->
|
||||
<script>
|
||||
|
||||
/**
|
||||
* 备份
|
||||
*/
|
||||
function btn_backup(type) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/admin/backup/doBackup',
|
||||
async: false,
|
||||
data: {
|
||||
'type' : type
|
||||
},
|
||||
success: function (data) {
|
||||
if(data.code==1){
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'success',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff',
|
||||
afterHidden: function () {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'error',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 2000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送备份到邮箱
|
||||
*
|
||||
* @param fileName
|
||||
* @param type
|
||||
*/
|
||||
function sendToEmail(fileName,type) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/admin/backup/sendToEmail',
|
||||
async: false,
|
||||
data: {
|
||||
'type' : type,
|
||||
'fileName' : fileName
|
||||
},
|
||||
success: function (data) {
|
||||
if(data.code==1){
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'success',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff',
|
||||
afterHidden: function () {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'error',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 2000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除备份
|
||||
*/
|
||||
function delBackup(fileName,type) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/admin/backup/delBackup',
|
||||
async: false,
|
||||
data: {
|
||||
'type' : type,
|
||||
'fileName' : fileName
|
||||
},
|
||||
success: function (data) {
|
||||
if(data.code==1){
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'success',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff',
|
||||
afterHidden: function () {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'error',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 2000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<#include "module/_footer.ftl">
|
||||
</div>
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
</a>
|
||||
<ul class="treeview-menu">
|
||||
<li><a data-pjax="true" href="/admin/option"><i class="fa fa-circle-o"></i>博客设置</a></li>
|
||||
<li><a data-pjax="true" href="/admin/backup"><i class="fa fa-circle-o"></i>备份恢复</a></li>
|
||||
<li><a data-pjax="true" href="/admin/backup"><i class="fa fa-circle-o"></i>博客备份</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<div class="emailpaged" style="background: #fff;">
|
||||
<div class="emailcontent" style="width:100%;max-width:720px;text-align: left;margin: 0 auto;padding-top: 20px;padding-bottom: 80px">
|
||||
<div class="emailtitle" style="border-radius: 5px;border:1px solid #eee;overflow: hidden;">
|
||||
<h1 style="color:#fff;background: #3798e8;line-height:70px;font-size:24px;font-weight:normal;padding-left:40px;margin:0">
|
||||
您有新的备份,请按需下载附件。
|
||||
</h1>
|
||||
<div class="emailtext" style="background:#fff;padding:20px 32px 40px;">
|
||||
备份详情:<br />
|
||||
文件名:${fileName}<br />
|
||||
备份时间:${createAt?string("yyyy-MM-dd HH:mm")}<br />
|
||||
文件大小:${size}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue