文章加密,订阅,本地上传文件与访问等

master
haitao 2 years ago
parent cb46d3ca98
commit eff348f031

@ -12,6 +12,7 @@ import com.ld.poetry.utils.CommonQuery;
import com.ld.poetry.utils.PoetryCache;
import com.ld.poetry.utils.PoetryEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@ -28,6 +29,9 @@ import java.util.stream.Collectors;
@Component
public class PoetryApplicationRunner implements ApplicationRunner {
@Value("${store.type}")
private String defaultType;
@Autowired
private WebInfoMapper webInfoMapper;
@ -51,6 +55,7 @@ public class PoetryApplicationRunner implements ApplicationRunner {
LambdaQueryChainWrapper<WebInfo> wrapper = new LambdaQueryChainWrapper<>(webInfoMapper);
List<WebInfo> list = wrapper.list();
if (!CollectionUtils.isEmpty(list)) {
list.get(0).setDefaultStoreType(defaultType);
PoetryCache.put(CommonConst.WEB_INFO, list.get(0));
}

@ -1,7 +1,10 @@
package com.ld.poetry.config;
import com.alibaba.fastjson.JSON;
import com.ld.poetry.utils.CodeMsg;
import com.ld.poetry.utils.CommonQuery;
import com.ld.poetry.utils.PoetryUtil;
import com.ld.poetry.utils.storage.FileFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@ -18,11 +21,23 @@ public class PoetryFilter extends OncePerRequestFilter {
@Autowired
private CommonQuery commonQuery;
@Autowired
private FileFilter fileFilter;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
try {
commonQuery.saveHistory(PoetryUtil.getIpAddr(httpServletRequest));
} catch (Exception e) {
if (!"OPTIONS".equals(httpServletRequest.getMethod())) {
try {
commonQuery.saveHistory(PoetryUtil.getIpAddr(httpServletRequest));
} catch (Exception e) {
}
if (fileFilter.doFilterFile(httpServletRequest, httpServletResponse)) {
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(JSON.toJSONString(PoetryResult.fail(CodeMsg.PARAMETER_ERROR.getCode(), CodeMsg.PARAMETER_ERROR.getMsg())));
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);

@ -40,7 +40,7 @@ public class SaveCheckAspect {
PoetryCache.put(CommonConst.SAVE_COUNT_USER_ID + user.getId().toString(), atomicInteger, CommonConst.SAVE_EXPIRE);
}
int userIdCount = atomicInteger.getAndIncrement();
if (userIdCount > CommonConst.SAVE_MAX_COUNT) {
if (userIdCount >= CommonConst.SAVE_MAX_COUNT) {
log.info("用户保存超限:" + user.getId().toString() + ",次数:" + userIdCount);
flag = true;
}

@ -76,12 +76,10 @@ public class ArticleController {
/**
*
* <p>
* flag = true
*/
@GetMapping("/getArticleById")
public PoetryResult<ArticleVO> getArticleById(@RequestParam("id") Integer id, @RequestParam("flag") Boolean flag, @RequestParam(value = "password", required = false) String password) {
return articleService.getArticleById(id, flag, password);
public PoetryResult<ArticleVO> getArticleById(@RequestParam("id") Integer id, @RequestParam(value = "password", required = false) String password) {
return articleService.getArticleById(id, password);
}
}

@ -3,8 +3,9 @@ package com.ld.poetry.controller;
import com.ld.poetry.config.LoginCheck;
import com.ld.poetry.config.PoetryResult;
import com.ld.poetry.config.SaveCheck;
import com.ld.poetry.utils.QiniuUtil;
import com.ld.poetry.utils.storage.QiniuUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@ -15,13 +16,14 @@ import org.springframework.web.bind.annotation.RestController;
*/
@RestController
@RequestMapping("/qiniu")
@ConditionalOnBean(QiniuUtil.class)
public class QiniuController {
@Autowired
private QiniuUtil qiniuUtil;
/**
*
*
*/
@GetMapping("/getUpToken")
@LoginCheck

@ -6,21 +6,18 @@ import com.ld.poetry.config.LoginCheck;
import com.ld.poetry.config.PoetryResult;
import com.ld.poetry.entity.Resource;
import com.ld.poetry.service.ResourceService;
import com.ld.poetry.utils.CommonConst;
import com.ld.poetry.utils.PoetryEnum;
import com.ld.poetry.utils.PoetryUtil;
import com.ld.poetry.utils.QiniuUtil;
import com.ld.poetry.utils.storage.StoreService;
import com.ld.poetry.utils.*;
import com.ld.poetry.utils.storage.FileStorageService;
import com.ld.poetry.vo.BaseRequestVO;
import com.ld.poetry.vo.FileVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -35,14 +32,11 @@ import java.util.stream.Collectors;
@RequestMapping("/resource")
public class ResourceController {
@Value("${qiniu.downloadUrl}")
private String downloadUrl;
@Autowired
private ResourceService resourceService;
@Autowired
private QiniuUtil qiniuUtil;
private FileStorageService fileStorageService;
/**
*
@ -58,44 +52,50 @@ public class ResourceController {
re.setType(resource.getType());
re.setSize(resource.getSize());
re.setMimeType(resource.getMimeType());
re.setStoreType(resource.getStoreType());
re.setUserId(PoetryUtil.getUserId());
resourceService.save(re);
return PoetryResult.success();
}
/**
*
*/
@PostMapping("/upload")
@LoginCheck
public PoetryResult<String> upload(@RequestParam("file") MultipartFile file, FileVO fileVO) {
if (file == null || !StringUtils.hasText(fileVO.getType()) || !StringUtils.hasText(fileVO.getRelativePath())) {
return PoetryResult.fail("文件和资源类型和资源路径不能为空!");
}
fileVO.setFile(file);
StoreService storeService = fileStorageService.getFileStorage(fileVO.getStoreType());
FileVO result = storeService.saveFile(fileVO);
Resource re = new Resource();
re.setPath(result.getVisitPath());
re.setType(fileVO.getType());
re.setSize(Integer.valueOf(Long.toString(file.getSize())));
re.setMimeType(file.getContentType());
re.setStoreType(fileVO.getStoreType());
re.setUserId(PoetryUtil.getUserId());
resourceService.save(re);
return PoetryResult.success(result.getVisitPath());
}
/**
*
*/
@PostMapping("/deleteResource")
@LoginCheck(0)
public PoetryResult deleteResource(@RequestParam("path") String path) {
qiniuUtil.deleteFile(Collections.singletonList(path.replace(downloadUrl, "")));
resourceService.lambdaUpdate().eq(Resource::getPath, path).remove();
return PoetryResult.success();
}
@GetMapping("/getResourceInfo")
@LoginCheck(0)
public PoetryResult getResourceInfo() {
List<Resource> resources = resourceService.lambdaQuery()
.select(Resource::getId, Resource::getPath)
.like(Resource::getPath, downloadUrl)
.isNull(Resource::getSize)
.list();
if (!CollectionUtils.isEmpty(resources)) {
Map<String, Integer> resourceMap = resources.stream().collect(Collectors.toMap(resource -> resource.getPath().replace(downloadUrl, ""), Resource::getId));
Map<String, Map<String, String>> fileInfo = qiniuUtil.getFileInfo(new ArrayList<>(resourceMap.keySet()));
if (!CollectionUtils.isEmpty(fileInfo)) {
List<Resource> collect = fileInfo.entrySet().stream().map(entry -> {
Resource resource = new Resource();
resource.setId(resourceMap.get(entry.getKey()));
resource.setSize(Integer.valueOf(entry.getValue().get("size")));
resource.setMimeType(entry.getValue().get("mimeType"));
return resource;
}).collect(Collectors.toList());
resourceService.updateBatchById(collect);
}
Resource resource = resourceService.lambdaQuery().select(Resource::getStoreType).eq(Resource::getPath, path).one();
if (resource == null) {
return PoetryResult.fail("文件不存在:" + path);
}
StoreService storeService = fileStorageService.getFileStorageByStoreType(resource.getStoreType());
storeService.deleteFile(Collections.singletonList(path));
return PoetryResult.success();
}

@ -151,5 +151,18 @@ public class UserController {
public PoetryResult<List<UserVO>> getUserByUsername(@RequestParam("username") String username) {
return userService.getUserByUsername(username);
}
/**
* /
* <p>
* flag = true
* flag = false
*/
@GetMapping("/subscribe")
@LoginCheck
public PoetryResult<UserVO> subscribe(@RequestParam("labelId") Integer labelId, @RequestParam("flag") Boolean flag) {
PoetryCache.remove(CommonConst.USER_CACHE + PoetryUtil.getUserId().toString());
return userService.subscribe(labelId, flag);
}
}

@ -17,6 +17,7 @@ import com.ld.poetry.vo.BaseRequestVO;
import com.ld.poetry.vo.ResourcePathVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@ -40,6 +41,9 @@ import java.util.stream.Collectors;
@RequestMapping("/webInfo")
public class WebInfoController {
@Value("${store.type}")
private String defaultType;
@Autowired
private WebInfoService webInfoService;
@ -73,6 +77,7 @@ public class WebInfoController {
LambdaQueryChainWrapper<WebInfo> wrapper = new LambdaQueryChainWrapper<>(webInfoService.getBaseMapper());
List<WebInfo> list = wrapper.list();
if (!CollectionUtils.isEmpty(list)) {
list.get(0).setDefaultStoreType(defaultType);
PoetryCache.put(CommonConst.WEB_INFO, list.get(0));
}
return PoetryResult.success();
@ -228,16 +233,39 @@ public class WebInfoController {
return PoetryResult.fail("信息不全!");
}
ResourcePath friend = new ResourcePath();
friend.setClassify(CommonConst.DEFAULT_FRIEND);
friend.setTitle(resourcePathVO.getTitle());
friend.setIntroduction(resourcePathVO.getIntroduction());
friend.setCover(resourcePathVO.getCover());
friend.setUrl(resourcePathVO.getUrl());
friend.setRemark(PoetryUtil.getUserId().toString());
friend.setType(CommonConst.RESOURCE_PATH_TYPE_FRIEND);
friend.setStatus(Boolean.FALSE);
resourcePathMapper.insert(friend);
return PoetryResult.success();
}
/**
*
*/
@GetMapping("/listFriend")
public PoetryResult<Map<String, List<ResourcePathVO>>> listFriend() {
LambdaQueryChainWrapper<ResourcePath> wrapper = new LambdaQueryChainWrapper<>(resourcePathMapper);
List<ResourcePath> resourcePaths = wrapper.eq(ResourcePath::getType, CommonConst.RESOURCE_PATH_TYPE_FRIEND)
.eq(ResourcePath::getStatus, Boolean.TRUE)
.orderByAsc(ResourcePath::getCreateTime)
.list();
Map<String, List<ResourcePathVO>> collect = new HashMap<>();
if (!CollectionUtils.isEmpty(resourcePaths)) {
collect = resourcePaths.stream().map(rp -> {
ResourcePathVO resourcePathVO = new ResourcePathVO();
BeanUtils.copyProperties(rp, resourcePathVO);
return resourcePathVO;
}).collect(Collectors.groupingBy(ResourcePathVO::getClassify));
}
return PoetryResult.success(collect);
}
/**
*

@ -70,6 +70,12 @@ public class Article implements Serializable {
@TableField("password")
private String password;
/**
*
*/
@TableField("tips")
private String tips;
/**
* [0:1:]
*/

@ -49,6 +49,12 @@ public class Resource implements Serializable {
@TableField("status")
private Boolean status;
/**
*
*/
@TableField("store_type")
private String storeType;
/**
*
*/

@ -82,6 +82,12 @@ public class User implements Serializable {
@TableField("admire")
private String admire;
/**
*
*/
@TableField("subscribe")
private String subscribe;
/**
*
*/

@ -101,4 +101,7 @@ public class WebInfo implements Serializable {
@TableField(exist = false)
private String historyDayCount;
@TableField(exist = false)
private String defaultStoreType;
}

@ -25,7 +25,7 @@ public interface ArticleService extends IService<Article> {
PoetryResult<Page> listArticle(BaseRequestVO baseRequestVO);
PoetryResult<ArticleVO> getArticleById(Integer id, Boolean flag, String password);
PoetryResult<ArticleVO> getArticleById(Integer id, String password);
PoetryResult<Page> listAdminArticle(BaseRequestVO baseRequestVO, Boolean isBoss);

@ -49,4 +49,6 @@ public interface UserService extends IService<User> {
PoetryResult<List<UserVO>> getUserByUsername(String username);
PoetryResult<UserVO> token(String userToken);
PoetryResult<UserVO> subscribe(Integer labelId, Boolean flag);
}

@ -1,21 +1,22 @@
package com.ld.poetry.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ld.poetry.config.PoetryResult;
import com.ld.poetry.dao.ArticleMapper;
import com.ld.poetry.entity.Article;
import com.ld.poetry.entity.Label;
import com.ld.poetry.entity.Sort;
import com.ld.poetry.entity.User;
import com.ld.poetry.dao.LabelMapper;
import com.ld.poetry.entity.*;
import com.ld.poetry.service.ArticleService;
import com.ld.poetry.service.UserService;
import com.ld.poetry.utils.*;
import com.ld.poetry.vo.ArticleVO;
import com.ld.poetry.vo.BaseRequestVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@ -42,6 +43,18 @@ public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> impl
@Autowired
private CommonQuery commonQuery;
@Autowired
private UserService userService;
@Autowired
private MailUtil mailUtil;
@Autowired
private LabelMapper labelMapper;
@Value("${user.subscribe.format}")
private String subscribeFormat;
@Override
public PoetryResult saveArticle(ArticleVO articleVO) {
if (articleVO.getViewStatus() != null && !articleVO.getViewStatus() && !StringUtils.hasText(articleVO.getPassword())) {
@ -53,6 +66,7 @@ public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> impl
}
if (articleVO.getViewStatus() != null && !articleVO.getViewStatus() && StringUtils.hasText(articleVO.getPassword())) {
article.setPassword(articleVO.getPassword());
article.setTips(articleVO.getTips());
}
article.setViewStatus(articleVO.getViewStatus());
article.setCommentStatus(articleVO.getCommentStatus());
@ -68,9 +82,41 @@ public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> impl
if (!CollectionUtils.isEmpty(sortInfo)) {
PoetryCache.put(CommonConst.SORT_INFO, sortInfo);
}
try {
if (articleVO.getViewStatus()) {
List<User> users = userService.lambdaQuery().select(User::getEmail, User::getSubscribe).eq(User::getUserStatus, PoetryEnum.STATUS_ENABLE.getCode()).list();
List<String> emails = users.stream().filter(u -> {
List<Integer> sub = JSON.parseArray(u.getSubscribe(), Integer.class);
return !CollectionUtils.isEmpty(sub) && sub.contains(articleVO.getLabelId());
}).map(User::getEmail).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(emails)) {
LambdaQueryChainWrapper<Label> wrapper = new LambdaQueryChainWrapper<>(labelMapper);
Label label = wrapper.select(Label::getLabelName).eq(Label::getId, articleVO.getLabelId()).one();
String text = getSubscribeMail(label.getLabelName(), articleVO.getArticleTitle());
WebInfo webInfo = (WebInfo) PoetryCache.get(CommonConst.WEB_INFO);
mailUtil.sendMailMessage(emails, "您有一封来自" + (webInfo == null ? "Poetize" : webInfo.getWebName()) + "的回执!", text);
}
}
} catch (Exception e) {
log.error("订阅邮件发送失败:", e);
}
return PoetryResult.success();
}
private String getSubscribeMail(String labelName, String articleTitle) {
WebInfo webInfo = (WebInfo) PoetryCache.get(CommonConst.WEB_INFO);
String webName = (webInfo == null ? "Poetize" : webInfo.getWebName());
return String.format(mailUtil.getMailText(),
webName,
String.format(MailUtil.notificationMail, PoetryUtil.getAdminUser().getUsername()),
PoetryUtil.getAdminUser().getUsername(),
String.format(subscribeFormat, labelName, articleTitle),
"",
webName);
}
@Override
public PoetryResult deleteArticle(Integer id) {
Integer userId = PoetryUtil.getUserId();
@ -113,6 +159,7 @@ public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> impl
if (articleVO.getViewStatus() != null && !articleVO.getViewStatus() && StringUtils.hasText(articleVO.getPassword())) {
updateChainWrapper.set(Article::getPassword, articleVO.getPassword());
updateChainWrapper.set(StringUtils.hasText(articleVO.getTips()), Article::getTips, articleVO.getTips());
}
if (articleVO.getViewStatus() != null) {
updateChainWrapper.set(Article::getViewStatus, articleVO.getViewStatus());
@ -139,7 +186,6 @@ public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> impl
}
LambdaQueryChainWrapper<Article> lambdaQuery = lambdaQuery();
lambdaQuery.eq(Article::getViewStatus, PoetryEnum.STATUS_ENABLE.getCode());
lambdaQuery.in(!CollectionUtils.isEmpty(ids), Article::getId, ids);
lambdaQuery.like(StringUtils.hasText(baseRequestVO.getSearchKey()), Article::getArticleTitle, baseRequestVO.getSearchKey());
lambdaQuery.eq(baseRequestVO.getRecommendStatus() != null && baseRequestVO.getRecommendStatus(), Article::getRecommendStatus, PoetryEnum.STATUS_ENABLE.getCode());
@ -186,21 +232,17 @@ public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> impl
}
@Override
public PoetryResult<ArticleVO> getArticleById(Integer id, Boolean flag, String password) {
public PoetryResult<ArticleVO> getArticleById(Integer id, String password) {
LambdaQueryChainWrapper<Article> lambdaQuery = lambdaQuery();
lambdaQuery.eq(Article::getId, id);
if (flag) {
lambdaQuery.eq(Article::getViewStatus, PoetryEnum.STATUS_ENABLE.getCode());
} else {
if (!StringUtils.hasText(password)) {
return PoetryResult.fail("请输入文章密码!");
}
lambdaQuery.eq(Article::getPassword, password);
}
Article article = lambdaQuery.one();
if (article == null) {
return PoetryResult.success();
}
if (!article.getViewStatus() && (!StringUtils.hasText(password) || !password.equals(article.getPassword()))) {
return PoetryResult.fail("密码错误" + (StringUtils.hasText(article.getTips()) ? article.getTips() : "请联系作者获取密码"));
}
article.setPassword(null);
articleMapper.updateViewCount(id);
ArticleVO articleVO = buildArticleVO(article, false);

@ -1,6 +1,7 @@
package com.ld.poetry.service.impl;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -74,11 +75,11 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
password = new String(SecureUtil.aes(CommonConst.CRYPOTJS_KEY.getBytes(StandardCharsets.UTF_8)).decrypt(password));
User one = lambdaQuery().and(wrapper -> wrapper
.eq(User::getUsername, account)
.or()
.eq(User::getEmail, account)
.or()
.eq(User::getPhoneNumber, account))
.eq(User::getUsername, account)
.or()
.eq(User::getEmail, account)
.or()
.eq(User::getPhoneNumber, account))
.eq(User::getPassword, DigestUtils.md5DigestAsHex(password.getBytes()))
.one();
@ -278,6 +279,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
UserVO userVO = new UserVO();
BeanUtils.copyProperties(one, userVO);
userVO.setPassword(null);
userVO.setAccessToken(PoetryUtil.getToken());
return PoetryResult.success(userVO);
}
@ -545,6 +547,44 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return PoetryResult.success(userVO);
}
@Override
public PoetryResult<UserVO> subscribe(Integer labelId, Boolean flag) {
UserVO userVO = null;
User one = lambdaQuery().eq(User::getId, PoetryUtil.getUserId()).one();
List<Integer> sub = JSON.parseArray(one.getSubscribe(), Integer.class);
if (sub == null) sub = new ArrayList<>();
if (flag) {
if (!sub.contains(labelId)) {
sub.add(labelId);
User user = new User();
user.setId(one.getId());
user.setSubscribe(JSON.toJSONString(sub));
updateById(user);
userVO = new UserVO();
BeanUtils.copyProperties(one, userVO);
userVO.setPassword(null);
userVO.setSubscribe(user.getSubscribe());
userVO.setAccessToken(PoetryUtil.getToken());
}
} else {
if (sub.contains(labelId)) {
sub.remove(labelId);
User user = new User();
user.setId(one.getId());
user.setSubscribe(JSON.toJSONString(sub));
updateById(user);
userVO = new UserVO();
BeanUtils.copyProperties(one, userVO);
userVO.setPassword(null);
userVO.setSubscribe(user.getSubscribe());
userVO.setAccessToken(PoetryUtil.getToken());
}
}
return PoetryResult.success(userVO);
}
private String getCodeMail(int i) {
WebInfo webInfo = (WebInfo) PoetryCache.get(CommonConst.WEB_INFO);
String webName = (webInfo == null ? "Poetize" : webInfo.getWebName());

@ -230,4 +230,9 @@ public class CommonConst {
public static final String WEIYAN_TYPE_FRIEND = "friend";
public static final String WEIYAN_TYPE_NEWS = "news";
/**
*
*/
public static final String DEFAULT_FRIEND = "\uD83E\uDD47友情链接";
}

@ -42,6 +42,7 @@ public class MailUtil {
public static final String messageMail = "你收到来自 %s 的留言";
public static final String loveMail = "你收到来自 %s 的祝福";
public static final String imMail = "你收到来自 %s 的消息";
public static final String notificationMail = "你收到来自 %s 的订阅";
@Autowired
private JavaMailSender mailSender;
@ -49,9 +50,6 @@ public class MailUtil {
@Value("${spring.mail.username}")
private String sendMailer;
@Value("${qiniu.downloadUrl}")
private String downloadUrl;
/**
* 1.
* 2.

@ -1,5 +1,7 @@
package com.ld.poetry.utils;
import org.springframework.util.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -29,4 +31,22 @@ public class StringUtil {
return matcher.find();
}
public static boolean isValidFileName(String fileName) {
if (!StringUtils.hasText(fileName) || fileName.length() > 128) {
return false;
}
// 此示例允许文件名包含字母、数字、下划线、点号和连字符(减号),且不能以点号、下划线和连字符(减号)开头
String regex = "^(?![-._])[a-zA-Z0-9-._]+$";
return fileName.matches(regex);
}
public static boolean isValidDirectoryName(String directoryName) {
if (!StringUtils.hasText(directoryName) || directoryName.length() > 128) {
return false;
}
// 此示例允许目录名称只包含字母、数字、下划线和连字符(减号)
String regex = "^[a-zA-Z0-9-_]+$";
return directoryName.matches(regex);
}
}

@ -0,0 +1,54 @@
package com.ld.poetry.utils.storage;
import com.ld.poetry.entity.User;
import com.ld.poetry.utils.CommonConst;
import com.ld.poetry.utils.PoetryCache;
import com.ld.poetry.utils.PoetryUtil;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class FileFilter {
private final AntPathMatcher matcher = new AntPathMatcher();
public boolean doFilterFile(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
if (matcher.match("/resource/upload", httpServletRequest.getRequestURI())) {
String token = PoetryUtil.getToken();
if (StringUtils.hasText(token)) {
User user = (User) PoetryCache.get(token);
if (user != null) {
if (user.getId().intValue() == PoetryUtil.getAdminUser().getId().intValue()) {
return false;
}
AtomicInteger atomicInteger = (AtomicInteger) PoetryCache.get(CommonConst.SAVE_COUNT_USER_ID + user.getId().toString());
if (atomicInteger == null) {
atomicInteger = new AtomicInteger();
PoetryCache.put(CommonConst.SAVE_COUNT_USER_ID + user.getId().toString(), atomicInteger, CommonConst.SAVE_EXPIRE);
}
int userIdCount = atomicInteger.getAndIncrement();
String ip = PoetryUtil.getIpAddr(PoetryUtil.getRequest());
AtomicInteger atomic = (AtomicInteger) PoetryCache.get(CommonConst.SAVE_COUNT_IP + ip);
if (atomic == null) {
atomic = new AtomicInteger();
PoetryCache.put(CommonConst.SAVE_COUNT_IP + ip, atomic, CommonConst.SAVE_EXPIRE);
}
int ipCount = atomic.getAndIncrement();
return userIdCount >= CommonConst.SAVE_MAX_COUNT || ipCount >= CommonConst.SAVE_MAX_COUNT;
}
}
return true;
} else {
return false;
}
}
}

@ -0,0 +1,65 @@
package com.ld.poetry.utils.storage;
import com.ld.poetry.handle.PoetryRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
@Slf4j
@Component
public class FileStorageService implements ApplicationContextAware {
@Value("${store.type}")
private String defaultType;
private final Map<String, StoreService> storeServiceMap = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, StoreService> consumeService = applicationContext.getBeansOfType(StoreService.class);
if (!CollectionUtils.isEmpty(consumeService)) {
for (StoreService value : consumeService.values()) {
storeServiceMap.put(value.getStoreName(), value);
}
}
}
/**
*
*/
public StoreService getFileStorageByStoreType(String storeType) {
if (!StringUtils.hasText(storeType) || !storeServiceMap.containsKey(storeType)) {
throw new PoetryRuntimeException("没有找到对应的存储平台:" + storeType);
}
return storeServiceMap.get(storeType);
}
/**
* 使
*/
public StoreService getFileStorage(String storeType) {
if (!StringUtils.hasText(storeType)) {
storeType = defaultType;
}
if (!storeServiceMap.containsKey(storeType)) {
throw new PoetryRuntimeException("没有找到对应的存储平台:" + storeType);
}
return storeServiceMap.get(storeType);
}
}

@ -0,0 +1,101 @@
package com.ld.poetry.utils.storage;
import cn.hutool.core.io.FileUtil;
import com.ld.poetry.entity.Resource;
import com.ld.poetry.handle.PoetryRuntimeException;
import com.ld.poetry.service.ResourceService;
import com.ld.poetry.utils.StringUtil;
import com.ld.poetry.vo.FileVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
@Slf4j
@Component
@ConditionalOnProperty(name = "local.enable", havingValue = "true")
public class LocalUtil implements StoreService {
@Value("${local.uploadUrl}")
private String uploadUrl;
@Value("${local.downloadUrl}")
private String downloadUrl;
@Autowired
private ResourceService resourceService;
@Override
public void deleteFile(List<String> files) {
if (CollectionUtils.isEmpty(files)) {
return;
}
for (String filePath : files) {
File file = new File(filePath.replace(downloadUrl, uploadUrl));
if (file.exists() && file.isFile()) {
if (file.delete()) {
log.info("文件删除成功:" + filePath);
resourceService.lambdaUpdate().eq(Resource::getPath, filePath).remove();
} else {
log.error("文件删除失败:" + filePath);
}
} else {
log.error("文件不存在或者不是一个文件:" + filePath);
}
}
}
@Override
public FileVO saveFile(FileVO fileVO) {
if (!StringUtils.hasText(fileVO.getRelativePath()) ||
fileVO.getRelativePath().startsWith("/") ||
fileVO.getRelativePath().endsWith("/")) {
throw new PoetryRuntimeException("文件路径不合法!");
}
String path = fileVO.getRelativePath();
if (path.contains("/")) {
String[] split = path.split("/");
if (split.length > 5) {
throw new PoetryRuntimeException("文件路径不合法!");
}
for (int i = 0; i < split.length - 1; i++) {
if (!StringUtil.isValidDirectoryName(split[i])) {
throw new PoetryRuntimeException("文件路径不合法!");
}
}
if (!StringUtil.isValidFileName(split[split.length - 1])) {
throw new PoetryRuntimeException("文件路径不合法!");
}
}
String absolutePath = uploadUrl + path;
if (FileUtil.exist(absolutePath)) {
throw new PoetryRuntimeException("文件已存在!");
}
try {
File newFile = FileUtil.touch(absolutePath);
fileVO.getFile().transferTo(newFile);
FileVO result = new FileVO();
result.setAbsolutePath(absolutePath);
result.setVisitPath(downloadUrl + path);
return result;
} catch (IOException e) {
log.error("文件上传失败:", e);
FileUtil.del(absolutePath);
throw new PoetryRuntimeException("文件上传失败!");
}
}
@Override
public String getStoreName() {
return StoreEnum.LOCAL.getCode();
}
}

@ -1,7 +1,9 @@
package com.ld.poetry.utils;
package com.ld.poetry.utils.storage;
import com.ld.poetry.entity.Resource;
import com.ld.poetry.service.ResourceService;
import com.ld.poetry.utils.CommonConst;
import com.ld.poetry.vo.FileVO;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
@ -14,6 +16,7 @@ import com.qiniu.util.StringMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@ -25,7 +28,8 @@ import java.util.stream.Collectors;
@Slf4j
@Component
public class QiniuUtil {
@ConditionalOnProperty(name = "qiniu.enable", havingValue = "true")
public class QiniuUtil implements StoreService {
/**
*
@ -55,14 +59,19 @@ public class QiniuUtil {
return auth.uploadToken(bucket, key, EXPIRE_SECONDS, putPolicy);
}
@Override
public void deleteFile(List<String> files) {
if (CollectionUtils.isEmpty(files)) {
return;
}
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Region.region0());
Auth auth = Auth.create(accessKey, secretKey);
BucketManager bucketManager = new BucketManager(auth, cfg);
try {
//单次批量请求的文件数量不得超过1000
String[] keyList = files.toArray(new String[0]);
String[] keyList = files.stream().map(path -> path.replace(downloadUrl, "")).toArray(String[]::new);
BucketManager.BatchOperations batchOperations = new BucketManager.BatchOperations();
batchOperations.addDeleteOp(bucket, keyList);
Response response = bucketManager.batch(batchOperations);
@ -71,16 +80,27 @@ public class QiniuUtil {
BatchStatus status = batchStatusList[i];
String key = keyList[i];
if (status.code == 200) {
log.info(key + ":删除成功!");
log.info("文件删除成功:" + key);
resourceService.lambdaUpdate().eq(Resource::getPath, downloadUrl + key).remove();
} else {
log.error(key + "" + status.data.error);
log.error("文件删除失败:" + key + ",原因" + status.data.error);
}
}
} catch (QiniuException ex) {
log.error(ex.response.toString());
log.error("文件删除失败:" + ex.response.toString());
}
}
@Override
public FileVO saveFile(FileVO fileVO) {
return null;
}
@Override
public String getStoreName() {
return StoreEnum.QINIU.getCode();
}
public Map<String, Map<String, String>> getFileInfo(List<String> files) {
Map<String, Map<String, String>> result = new HashMap<>();
@ -143,6 +163,7 @@ public class QiniuUtil {
re.setType(CommonConst.PATH_TYPE_ASSETS);
re.setSize(Integer.valueOf(Long.toString(item.fsize)));
re.setMimeType(item.mimeType);
re.setStoreType(StoreEnum.QINIU.getCode());
re.setUserId(CommonConst.ADMIN_USER_ID);
resources.add(re);
}

@ -0,0 +1,32 @@
package com.ld.poetry.utils.storage;
import com.ld.poetry.handle.PoetryRuntimeException;
import org.springframework.util.StringUtils;
public enum StoreEnum {
QINIU("qiniu"),
LOCAL("local");
private String code;
StoreEnum(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public static StoreEnum existCode(String code) {
if (StringUtils.hasText(code)) {
StoreEnum[] values = StoreEnum.values();
for (StoreEnum typeEnum : values) {
if (typeEnum.getCode().equals(code)) {
return typeEnum;
}
}
}
throw new PoetryRuntimeException("存储平台不支持:" + code);
}
}

@ -0,0 +1,17 @@
package com.ld.poetry.utils.storage;
import com.ld.poetry.vo.FileVO;
import java.util.List;
/**
*
*/
public interface StoreService {
void deleteFile(List<String> files);
FileVO saveFile(FileVO fileVO);
String getStoreName();
}

@ -36,6 +36,8 @@ public class ArticleVO {
private String password;
private String tips;
private Boolean viewStatus;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")

@ -0,0 +1,20 @@
package com.ld.poetry.vo;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
public class FileVO {
private String type;
private String storeType;
private String relativePath;
private String absolutePath;
private String visitPath;
private MultipartFile file;
}

@ -28,6 +28,8 @@ public class UserVO {
private String introduction;
private String subscribe;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;

@ -1,5 +1,10 @@
server:
port: 8081
tomcat:
threads.max: 50
max-connections: 100
max-http-form-post-size: 35MB
connection-timeout: 60000
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
@ -11,6 +16,11 @@ mybatis-plus:
logic-not-delete-value: 0
spring:
servlet:
multipart:
max-file-size: 30MB
max-request-size: 30MB
mail:
host: smtp.qq.com
username: $$$$邮箱地址
@ -37,8 +47,19 @@ spring:
user:
code:
format: 【poetize.cn】%s为本次验证的验证码请在5分钟内完成验证。为保证账号安全请勿泄漏此验证码。
subscribe:
format: 【poetize.cn】您订阅的专栏【%s】新增一篇文章%s。
store:
type: qiniu
local:
enable: true
uploadUrl: $$$$服务器上传路径,仿照【/home/file/】
downloadUrl: $$$$服务器访问路径仿照【https://poetize.cn/static/】
qiniu:
enable: true
accessKey: $$$$七牛云accessKey
secretKey: $$$$七牛云secretKey
bucket: $$$$七牛云存储空间

@ -17,6 +17,7 @@
<result column="recommend_status" property="recommendStatus"/>
<result column="view_status" property="viewStatus"/>
<result column="password" property="password"/>
<result column="tips" property="tips"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="update_by" property="updateBy"/>
@ -25,7 +26,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, user_id, sort_id, label_id, article_cover, article_title, article_content, password, view_status, recommend_status, view_count, like_count, comment_status, create_time, update_time, update_by, deleted
id, user_id, sort_id, label_id, article_cover, article_title, article_content, password, tips, view_status, recommend_status, view_count, like_count, comment_status, create_time, update_time, update_by, deleted
</sql>
</mapper>

@ -10,13 +10,14 @@
<result column="size" property="size"/>
<result column="mime_type" property="mimeType"/>
<result column="path" property="path"/>
<result column="store_type" property="storeType"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, user_id, type, size, mime_type, path, status, create_time
id, user_id, type, size, mime_type, path, store_type, status, create_time
</sql>
</mapper>

@ -14,6 +14,7 @@
<result column="open_id" property="openId"/>
<result column="avatar" property="avatar"/>
<result column="admire" property="admire"/>
<result column="subscribe" property="subscribe"/>
<result column="introduction" property="introduction"/>
<result column="user_type" property="userType"/>
<result column="create_time" property="createTime"/>
@ -24,7 +25,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, username, password, phone_number, email, admire, user_status, gender, open_id, avatar, introduction, user_type, create_time, update_time, update_by, deleted
id, username, password, phone_number, email, admire, subscribe, user_status, gender, open_id, avatar, introduction, user_type, create_time, update_time, update_by, deleted
</sql>
</mapper>

@ -21,6 +21,7 @@ CREATE TABLE `user` (
`open_id` varchar(128) DEFAULT NULL COMMENT 'openId',
`avatar` varchar(256) DEFAULT NULL COMMENT '头像',
`admire` varchar(32) DEFAULT NULL COMMENT '赞赏',
`subscribe` text DEFAULT NULL COMMENT '订阅',
`introduction` varchar(4096) DEFAULT NULL COMMENT '简介',
`user_type` tinyint(2) NOT NULL DEFAULT 2 COMMENT '用户类型[0:admin1:管理员2:普通用户]',
@ -47,6 +48,7 @@ CREATE TABLE `article` (
`like_count` int NOT NULL DEFAULT 0 COMMENT '点赞数',
`view_status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否可见[0:否1:是]',
`password` varchar(128) DEFAULT NULL COMMENT '密码',
`tips` varchar(128) DEFAULT NULL COMMENT '提示',
`recommend_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否推荐[0:否1:是]',
`comment_status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用评论[0:否1:是]',
@ -177,6 +179,7 @@ CREATE TABLE `resource` (
`size` int DEFAULT NULL COMMENT '资源内容的大小,单位:字节',
`mime_type` varchar(256) DEFAULT NULL COMMENT '资源的 MIME 类型',
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用[0:否1:是]',
`store_type` varchar(16) DEFAULT NULL COMMENT '存储平台',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
@ -301,7 +304,7 @@ CREATE TABLE `im_chat_user_group_message` (
-- 第三步:执行初始化语句
INSERT INTO `user` (`id`, `username`, `password`, `phone_number`, `email`, `user_status`, `gender`, `open_id`, `avatar`, `introduction`, `user_type`, `update_by`, `deleted`, `admire`) VALUES(1, 'Sara', '47bce5c74f589f4867dbd57e9ca9f808', '', '', 1, 1, '', '', '', 0, 'Sara', 0, '');
INSERT INTO `user` (`id`, `username`, `password`, `phone_number`, `email`, `user_status`, `gender`, `open_id`, `avatar`, `introduction`, `user_type`, `update_by`, `deleted`, `admire`, `subscribe`) VALUES(1, 'Sara', '47bce5c74f589f4867dbd57e9ca9f808', '', '', 1, 1, '', '', '', 0, 'Sara', 0, '', '');
INSERT INTO `web_info` (`id`, `web_name`, `web_title`, `notices`, `footer`, `background_image`, `avatar`, `random_avatar`, `random_name`, `random_cover`, `waifu_json`, `status`) VALUES(1, 'Sara', 'Poetize', '[]', '云想衣裳花想容, 春风拂槛露华浓。', '', '', '[]', '[]', '[]', '{
"mouseover": [

Loading…
Cancel
Save