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);
|
cacheStore.putAny("preview-post-token-" + postId, token, 10, TimeUnit.MINUTES);
|
||||||
|
|
||||||
// build preview post url
|
// 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
|
// 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);
|
cacheStore.putAny("preview-sheet-token-" + sheetId, token, 10, TimeUnit.MINUTES);
|
||||||
|
|
||||||
// build preview sheet url
|
// 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
|
// redirect to preview url
|
||||||
response.sendRedirect(url);
|
response.sendRedirect(redirect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package run.halo.app.controller.content;
|
package run.halo.app.controller.content;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
import cn.hutool.core.util.PageUtil;
|
import cn.hutool.core.util.PageUtil;
|
||||||
|
import cn.hutool.crypto.digest.BCrypt;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
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.data.web.SortDefault;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import run.halo.app.cache.StringCacheStore;
|
import run.halo.app.cache.StringCacheStore;
|
||||||
|
import run.halo.app.cache.lock.CacheLock;
|
||||||
import run.halo.app.exception.ForbiddenException;
|
import run.halo.app.exception.ForbiddenException;
|
||||||
import run.halo.app.model.entity.Category;
|
import run.halo.app.model.entity.Category;
|
||||||
import run.halo.app.model.entity.Post;
|
import run.halo.app.model.entity.Post;
|
||||||
|
@ -111,10 +111,19 @@ public class ContentArchiveController {
|
||||||
@GetMapping("{url}")
|
@GetMapping("{url}")
|
||||||
public String post(@PathVariable("url") String url,
|
public String post(@PathVariable("url") String url,
|
||||||
@RequestParam(value = "preview", required = false, defaultValue = "false") boolean preview,
|
@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,
|
@RequestParam(value = "token", required = false) String token,
|
||||||
Model model) {
|
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) {
|
if (preview) {
|
||||||
// render markdown to html when preview post
|
// render markdown to html when preview post
|
||||||
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
|
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.getNextPost(post.getCreateTime()).ifPresent(nextPost -> model.addAttribute("nextPost", nextPost));
|
||||||
postService.getPrePost(post.getCreateTime()).ifPresent(prePost -> model.addAttribute("prePost", prePost));
|
postService.getPrePost(post.getCreateTime()).ifPresent(prePost -> model.addAttribute("prePost", prePost));
|
||||||
|
|
||||||
|
@ -145,4 +163,37 @@ public class ContentArchiveController {
|
||||||
|
|
||||||
return themeService.render("post");
|
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 Boolean disallowComment;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
private String template;
|
private String template;
|
||||||
|
|
||||||
private Integer topPriority = 0;
|
private Integer topPriority = 0;
|
||||||
|
|
|
@ -13,9 +13,9 @@ public enum JournalType implements ValueEnum<Integer> {
|
||||||
PUBLIC(1),
|
PUBLIC(1),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private type.
|
* Intimate type.
|
||||||
*/
|
*/
|
||||||
PRIVATE(0);
|
INTIMATE(0);
|
||||||
|
|
||||||
private final int value;
|
private final int value;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,12 @@ public enum PostStatus implements ValueEnum<Integer> {
|
||||||
/**
|
/**
|
||||||
* Recycle status.
|
* Recycle status.
|
||||||
*/
|
*/
|
||||||
RECYCLE(2);
|
RECYCLE(2),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intimate status
|
||||||
|
*/
|
||||||
|
INTIMATE(3);
|
||||||
|
|
||||||
private final int value;
|
private final int value;
|
||||||
|
|
||||||
|
|
|
@ -217,6 +217,11 @@ public abstract class BasePostServiceImpl<POST extends BasePost> extends Abstrac
|
||||||
post.setFormatContent(MarkdownUtils.renderHtml(post.getOriginalContent()));
|
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
|
// Create or update post
|
||||||
if (ServiceUtils.isEmptyId(post.getId())) {
|
if (ServiceUtils.isEmptyId(post.getId())) {
|
||||||
// The sheet will be created
|
// 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