mirror of https://github.com/halo-dev/halo
Support set password to post.
parent
6564710453
commit
83789d52df
|
@ -150,9 +150,9 @@ public class PostController {
|
|||
cacheStore.putAny("preview-post-token-" + postId, token, 10, TimeUnit.MINUTES);
|
||||
|
||||
// build preview post url
|
||||
String url = String.format("%s/archives/%s?preview=true&token=%s", optionService.getBlogBaseUrl(), post.getUrl(), token);
|
||||
String redirect = String.format("%s/archives/%s?preview=true&token=%s", optionService.getBlogBaseUrl(), post.getUrl(), token);
|
||||
|
||||
// redirect to preview url
|
||||
response.sendRedirect(url);
|
||||
response.sendRedirect(redirect);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,9 +123,9 @@ public class SheetController {
|
|||
cacheStore.putAny("preview-sheet-token-" + sheetId, token, 10, TimeUnit.MINUTES);
|
||||
|
||||
// build preview sheet url
|
||||
String url = String.format("%s/s/%s?preview=true&token=%s", optionService.getBlogBaseUrl(), sheet.getUrl(), token);
|
||||
String redirect = String.format("%s/s/%s?preview=true&token=%s", optionService.getBlogBaseUrl(), sheet.getUrl(), token);
|
||||
|
||||
// redirect to preview url
|
||||
response.sendRedirect(url);
|
||||
response.sendRedirect(redirect);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package run.halo.app.controller.content;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
@ -9,11 +11,9 @@ import org.springframework.data.domain.Sort;
|
|||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import run.halo.app.cache.StringCacheStore;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.entity.Post;
|
||||
|
@ -111,10 +111,19 @@ public class ContentArchiveController {
|
|||
@GetMapping("{url}")
|
||||
public String post(@PathVariable("url") String url,
|
||||
@RequestParam(value = "preview", required = false, defaultValue = "false") boolean preview,
|
||||
@RequestParam(value = "intimate", required = false, defaultValue = "false") boolean intimate,
|
||||
@RequestParam(value = "token", required = false) String token,
|
||||
Model model) {
|
||||
Post post = postService.getBy(preview ? PostStatus.DRAFT : PostStatus.PUBLISHED, url);
|
||||
Post post;
|
||||
if (preview) {
|
||||
post = postService.getBy(PostStatus.DRAFT, url);
|
||||
} else if (intimate) {
|
||||
post = postService.getBy(PostStatus.INTIMATE, url);
|
||||
} else {
|
||||
post = postService.getBy(PostStatus.PUBLISHED, url);
|
||||
}
|
||||
|
||||
// if this is a preview url.
|
||||
if (preview) {
|
||||
// render markdown to html when preview post
|
||||
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
|
||||
|
@ -127,6 +136,15 @@ public class ContentArchiveController {
|
|||
}
|
||||
}
|
||||
|
||||
// if this is a intimate url.
|
||||
if (intimate) {
|
||||
// verify token
|
||||
String cachedToken = cacheStore.getAny(token, String.class).orElseThrow(() -> new ForbiddenException("您没有该文章的访问权限"));
|
||||
if (!cachedToken.equals(token)) {
|
||||
throw new ForbiddenException("您没有该文章的访问权限");
|
||||
}
|
||||
}
|
||||
|
||||
postService.getNextPost(post.getCreateTime()).ifPresent(nextPost -> model.addAttribute("nextPost", nextPost));
|
||||
postService.getPrePost(post.getCreateTime()).ifPresent(prePost -> model.addAttribute("prePost", prePost));
|
||||
|
||||
|
@ -145,4 +163,37 @@ public class ContentArchiveController {
|
|||
|
||||
return themeService.render("post");
|
||||
}
|
||||
|
||||
@GetMapping(value = "{url}/password")
|
||||
public String password(@PathVariable("url") String url,
|
||||
Model model) {
|
||||
Post post = postService.getBy(PostStatus.INTIMATE, url);
|
||||
if (null == post) {
|
||||
throw new ForbiddenException("没有查询到该文章信息");
|
||||
}
|
||||
|
||||
model.addAttribute("url", url);
|
||||
return "common/template/post_password";
|
||||
}
|
||||
|
||||
@PostMapping(value = "{url}/password")
|
||||
@CacheLock
|
||||
public String password(@PathVariable("url") String url,
|
||||
@RequestParam(value = "password") String password) {
|
||||
Post post = postService.getBy(PostStatus.INTIMATE, url);
|
||||
if (null == post) {
|
||||
throw new ForbiddenException("没有查询到该文章信息");
|
||||
}
|
||||
|
||||
if (BCrypt.checkpw(password, post.getPassword())) {
|
||||
String token = IdUtil.simpleUUID();
|
||||
cacheStore.putAny(token, token, 10, TimeUnit.SECONDS);
|
||||
|
||||
String redirect = String.format("%s/archives/%s?intimate=true&token=%s", optionService.getBlogBaseUrl(), post.getUrl(), token);
|
||||
return "redirect:" + redirect;
|
||||
} else {
|
||||
String redirect = String.format("%s/archives/%s/password", optionService.getBlogBaseUrl(), post.getUrl());
|
||||
return "redirect:" + redirect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ public class BasePostSimpleDTO extends BasePostMinimalDTO {
|
|||
|
||||
private Boolean disallowComment;
|
||||
|
||||
private String password;
|
||||
|
||||
private String template;
|
||||
|
||||
private Integer topPriority = 0;
|
||||
|
|
|
@ -13,9 +13,9 @@ public enum JournalType implements ValueEnum<Integer> {
|
|||
PUBLIC(1),
|
||||
|
||||
/**
|
||||
* Private type.
|
||||
* Intimate type.
|
||||
*/
|
||||
PRIVATE(0);
|
||||
INTIMATE(0);
|
||||
|
||||
private final int value;
|
||||
|
||||
|
|
|
@ -20,7 +20,12 @@ public enum PostStatus implements ValueEnum<Integer> {
|
|||
/**
|
||||
* Recycle status.
|
||||
*/
|
||||
RECYCLE(2);
|
||||
RECYCLE(2),
|
||||
|
||||
/**
|
||||
* Intimate status
|
||||
*/
|
||||
INTIMATE(3);
|
||||
|
||||
private final int value;
|
||||
|
||||
|
|
|
@ -217,6 +217,11 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
|
|||
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
|
||||
}
|
||||
|
||||
// if password is not empty,change status to intimate
|
||||
if (StringUtils.isNotEmpty(post.getPassword()) && post.getStatus() != PostStatus.DRAFT) {
|
||||
post.setStatus(PostStatus.INTIMATE);
|
||||
}
|
||||
|
||||
// Create or update post
|
||||
if (ServiceUtils.isEmptyId(post.getId())) {
|
||||
// The sheet will be created
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
|
||||
<meta name="robots" content="noindex,nofllow"/>
|
||||
<title>私密文章访问 - ${options.blog_title!}</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #080821;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -140px 0 0 -140px;
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.password-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.password-input input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
color: white;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
background-color: hsl(236, 32%, 26%);
|
||||
padding: 0.5em 1em;
|
||||
border: 1px solid transparent;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.password-input input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.password-input input::placeholder {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
|
||||
.password-input span {
|
||||
position: absolute;
|
||||
background-color: #fc2f70;
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
|
||||
.password-input .bottom,
|
||||
.password-input .top {
|
||||
height: 1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
.password-input .left,
|
||||
.password-input .right {
|
||||
width: 1px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
|
||||
.password-input .bottom {
|
||||
bottom: 0;
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
.password-input input:focus ~ .bottom {
|
||||
transform-origin: bottom left;
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.password-input .right {
|
||||
right: 0;
|
||||
transform-origin: top right;
|
||||
transition-delay: 0.05s;
|
||||
}
|
||||
|
||||
.password-input input:focus ~ .right {
|
||||
transform-origin: bottom right;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
.password-input .top {
|
||||
top: 0;
|
||||
transform-origin: top left;
|
||||
transition-delay: 0.15s;
|
||||
}
|
||||
|
||||
.password-input input:focus ~ .top {
|
||||
transform-origin: top right;
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.password-input .left {
|
||||
left: 0;
|
||||
transform-origin: bottom left;
|
||||
transition-delay: 0.25s;
|
||||
}
|
||||
|
||||
.password-input input:focus ~ .left {
|
||||
transform-origin: top left;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
.submit-input {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.submit-input button {
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
color: white;
|
||||
padding: 0.5em 1em;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
background-color: hsl(236, 32%, 26%);
|
||||
}
|
||||
|
||||
.submit-input button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.submit-input button::before {
|
||||
content: '';
|
||||
z-index: -1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 4px solid hsl(236, 32%, 26%);
|
||||
transform-origin: center;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.submit-input button:hover::before {
|
||||
transition: all 0.75s ease-in-out;
|
||||
transform-origin: center;
|
||||
transform: scale(1.75);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<form method="post" action="${context!}/archives/${url}/password">
|
||||
<div class="password-input">
|
||||
<input type="password" name="password" placeholder="请输入文章访问密码">
|
||||
<span class="bottom"></span>
|
||||
<span class="right"></span>
|
||||
<span class="top"></span>
|
||||
<span class="left"></span>
|
||||
</div>
|
||||
<div class="submit-input">
|
||||
<button type="submit">验证</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue