mirror of https://github.com/halo-dev/halo
feat: add basic functions of demo mode (#569)
* feat: add basic functions of demo mode * feat: add @disableApi annotation to some API that should be disabled * feat: add DisableApi test case * fix: change DisableApi annotation name to DisableOnConditation * Fix test cases * Add more strict test on failing case Co-authored-by: John Niang <johnniang@foxmail.com>pull/575/head
parent
df3601a751
commit
b78e2161e5
|
@ -6,6 +6,7 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.web.bind.annotation.*;
|
||||
import run.halo.app.Application;
|
||||
import run.halo.app.cache.lock.CacheLock;
|
||||
import run.halo.app.model.annotation.DisableOnCondition;
|
||||
import run.halo.app.model.dto.EnvironmentDTO;
|
||||
import run.halo.app.model.dto.StatisticDTO;
|
||||
import run.halo.app.model.params.LoginParam;
|
||||
|
@ -66,6 +67,7 @@ public class AdminController {
|
|||
}
|
||||
|
||||
@PutMapping("password/reset")
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Resets password by verify code")
|
||||
public void resetPassword(@RequestBody @Valid ResetPasswordParam param) {
|
||||
adminService.resetPasswordByCode(param);
|
||||
|
@ -104,12 +106,14 @@ public class AdminController {
|
|||
}
|
||||
|
||||
@PutMapping("spring/application.yaml")
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Updates application config content")
|
||||
public void updateSpringApplicationConfig(@RequestParam(name = "content") String content) {
|
||||
adminService.updateApplicationConfig(content);
|
||||
}
|
||||
|
||||
@PostMapping(value = {"halo/restart", "spring/restart"})
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Restarts halo server")
|
||||
public void restartApplication() {
|
||||
Application.restart();
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.springframework.data.domain.Pageable;
|
|||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import run.halo.app.model.annotation.DisableOnCondition;
|
||||
import run.halo.app.model.dto.OptionDTO;
|
||||
import run.halo.app.model.dto.OptionSimpleDTO;
|
||||
import run.halo.app.model.entity.Option;
|
||||
|
@ -43,6 +44,7 @@ public class OptionController {
|
|||
}
|
||||
|
||||
@PostMapping("saving")
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Saves options")
|
||||
public void saveOptions(@Valid @RequestBody List<OptionParam> optionParams) {
|
||||
optionService.save(optionParams);
|
||||
|
@ -73,12 +75,14 @@ public class OptionController {
|
|||
}
|
||||
|
||||
@PostMapping
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Creates option")
|
||||
public void createBy(@RequestBody @Valid OptionParam optionParam) {
|
||||
optionService.save(optionParam);
|
||||
}
|
||||
|
||||
@PutMapping("{optionId:\\d+}")
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Updates option")
|
||||
public void updateBy(@PathVariable("optionId") Integer optionId,
|
||||
@RequestBody @Valid OptionParam optionParam) {
|
||||
|
@ -86,12 +90,14 @@ public class OptionController {
|
|||
}
|
||||
|
||||
@DeleteMapping("{optionId:\\d+}")
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Deletes option")
|
||||
public void deletePermanently(@PathVariable("optionId") Integer optionId) {
|
||||
optionService.removePermanently(optionId);
|
||||
}
|
||||
|
||||
@PostMapping("map_view/saving")
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Saves options by option map")
|
||||
public void saveOptionsWithMapView(@RequestBody Map<String, Object> optionMap) {
|
||||
optionService.save(optionMap);
|
||||
|
|
|
@ -2,6 +2,7 @@ package run.halo.app.controller.admin.api;
|
|||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import run.halo.app.model.annotation.DisableOnCondition;
|
||||
import run.halo.app.model.dto.UserDTO;
|
||||
import run.halo.app.model.entity.User;
|
||||
import run.halo.app.model.params.PasswordParam;
|
||||
|
@ -49,6 +50,7 @@ public class UserController {
|
|||
}
|
||||
|
||||
@PutMapping("profiles/password")
|
||||
@DisableOnCondition
|
||||
@ApiOperation("Updates user's password")
|
||||
public BaseResponse updatePassword(@RequestBody @Valid PasswordParam passwordParam, User user) {
|
||||
userService.updatePassword(passwordParam.getOldPassword(), passwordParam.getNewPassword(), user.getId());
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package run.halo.app.handler.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.config.properties.HaloProperties;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.annotation.DisableOnCondition;
|
||||
import run.halo.app.model.enums.Mode;
|
||||
|
||||
/**
|
||||
* 自定义注解DisableApi的切面
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-02-14 14:08
|
||||
*/
|
||||
@Aspect
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DisableOnConditionAspect {
|
||||
|
||||
private final HaloProperties haloProperties;
|
||||
|
||||
public DisableOnConditionAspect(HaloProperties haloProperties) {
|
||||
this.haloProperties = haloProperties;
|
||||
}
|
||||
|
||||
@Pointcut("@annotation(run.halo.app.model.annotation.DisableOnCondition)")
|
||||
public void pointcut() {
|
||||
}
|
||||
|
||||
@Around("pointcut() && @annotation(disableApi)")
|
||||
public Object around(ProceedingJoinPoint joinPoint,
|
||||
DisableOnCondition disableApi) throws Throwable {
|
||||
Mode mode = disableApi.mode();
|
||||
if (haloProperties.getMode().equals(mode)) {
|
||||
throw new ForbiddenException("禁止访问");
|
||||
}
|
||||
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package run.halo.app.model.annotation;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import run.halo.app.model.enums.Mode;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 该注解可以限制某些条件下禁止访问api
|
||||
* @author guqing
|
||||
* @date 2020-02-14 13:48
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DisableOnCondition {
|
||||
@AliasFor("mode")
|
||||
Mode value() default Mode.DEMO;
|
||||
|
||||
@AliasFor("value")
|
||||
Mode mode() default Mode.DEMO;
|
||||
}
|
|
@ -13,6 +13,7 @@ import org.springframework.lang.Nullable;
|
|||
public enum Mode {
|
||||
PRODUCTION,
|
||||
DEVELOPMENT,
|
||||
DEMO,
|
||||
TEST;
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
server:
|
||||
port: 8090
|
||||
forward-headers-strategy: native
|
||||
compression:
|
||||
enabled: false
|
||||
spring:
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
devtools:
|
||||
add-properties: false
|
||||
output:
|
||||
ansi:
|
||||
enabled: always
|
||||
datasource:
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
|
||||
# H2 database configuration.
|
||||
driver-class-name: org.h2.Driver
|
||||
url: jdbc:h2:file:~/.halo/db/halo
|
||||
username: admin
|
||||
password: 123456
|
||||
|
||||
# MySQL database configuration.
|
||||
# driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
# url: jdbc:mysql://127.0.0.1:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
# username: root
|
||||
# password: 123456
|
||||
|
||||
h2:
|
||||
console:
|
||||
settings:
|
||||
web-allow-others: false
|
||||
path: /h2-console
|
||||
enabled: false
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: false
|
||||
open-in-view: false
|
||||
flyway:
|
||||
enabled: false
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 10240MB
|
||||
max-request-size: 10240MB
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
base-path: /api/admin/actuator
|
||||
exposure:
|
||||
include: ['httptrace', 'metrics','env','logfile','health']
|
||||
logging:
|
||||
level:
|
||||
run.halo.app: INFO
|
||||
file:
|
||||
path: ${user.home}/.halo/logs
|
||||
|
||||
halo:
|
||||
download-timeout: 5m
|
||||
doc-disabled: false
|
||||
production-env: false
|
||||
auth-enabled: true
|
||||
mode: demo
|
||||
workDir: ${user.home}/halo-demo/
|
||||
cache: level
|
|
@ -0,0 +1,29 @@
|
|||
package run.halo.app.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import run.halo.app.model.annotation.DisableOnCondition;
|
||||
import run.halo.app.model.support.BaseResponse;
|
||||
|
||||
/**
|
||||
* DisableApi注解测试类
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2020-02-14 17:51
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/admin/test/disableOnCondition")
|
||||
public class DisableOnConditionController {
|
||||
|
||||
@GetMapping("/no")
|
||||
@DisableOnCondition
|
||||
public BaseResponse<String> blockAccess() {
|
||||
return BaseResponse.ok("测试静止访问");
|
||||
}
|
||||
|
||||
@GetMapping("/yes")
|
||||
public BaseResponse<String> ableAccess() {
|
||||
return BaseResponse.ok("成功");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package run.halo.app.handler.aspect;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.util.NestedServletException;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.model.properties.PrimaryProperties;
|
||||
import run.halo.app.service.OptionService;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @date 2020-02-14 17:06
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "halo.auth-enabled=false")
|
||||
@ActiveProfiles("demo")
|
||||
@AutoConfigureMockMvc
|
||||
class DisableOnConditionAspectTest {
|
||||
|
||||
@Autowired
|
||||
MockMvc mvc;
|
||||
|
||||
@Autowired
|
||||
OptionService optionService;
|
||||
|
||||
static final String REQUEST_URI = "/api/admin/test/disableOnCondition";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
optionService.saveProperty(PrimaryProperties.IS_INSTALLED, "true");
|
||||
}
|
||||
|
||||
@Test()
|
||||
void blockAccessTest() throws Exception {
|
||||
Throwable t = null;
|
||||
try {
|
||||
mvc.perform(get(REQUEST_URI + "/no"))
|
||||
.andDo(print())
|
||||
.andReturn();
|
||||
} catch (NestedServletException nse) {
|
||||
t = nse;
|
||||
}
|
||||
Assertions.assertNotNull(t);
|
||||
final Throwable rootException = t;
|
||||
Assertions.assertThrows(ForbiddenException.class, () -> {
|
||||
throw rootException.getCause();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void ableAccessTest() throws Exception {
|
||||
mvc.perform(get(REQUEST_URI + "/yes"))
|
||||
.andDo(print())
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status", is(HttpStatus.OK.value())));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue