refactor: 迁移swagger到springdoc
This commit is contained in:
parent
fcbc78f405
commit
1d5bfb17ab
@ -54,7 +54,7 @@ public class LoginController {
|
||||
/**
|
||||
* 访问首页,提示语
|
||||
*/
|
||||
@RequestMapping("/")
|
||||
@GetMapping("/")
|
||||
@RateLimit(key = RateLimitKey.TEST_KEY, time = 10, maxCount = 5, cacheType = CacheType.Map,
|
||||
limitType = LimitType.GLOBAL)
|
||||
public String index() {
|
||||
|
||||
@ -12,6 +12,9 @@ import com.agileboot.infrastructure.cache.guava.GuavaCacheService;
|
||||
import com.agileboot.infrastructure.cache.map.MapCache;
|
||||
import com.agileboot.orm.common.enums.BusinessTypeEnum;
|
||||
import com.agileboot.orm.common.result.DictionaryData;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.List;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Positive;
|
||||
@ -35,6 +38,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RequestMapping("/system/config")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "配置API", description = "配置相关的增删查改")
|
||||
public class SysConfigController extends BaseController {
|
||||
|
||||
@NonNull
|
||||
@ -46,10 +50,11 @@ public class SysConfigController extends BaseController {
|
||||
/**
|
||||
* 获取参数配置列表
|
||||
*/
|
||||
@Operation(summary = "参数列表", description = "分页获取配置参数列表")
|
||||
@PreAuthorize("@permission.has('system:config:list')")
|
||||
@GetMapping("/list")
|
||||
public ResponseDTO<PageDTO> list(ConfigQuery query) {
|
||||
PageDTO page = configApplicationService.getConfigList(query);
|
||||
public ResponseDTO<PageDTO<ConfigDTO>> list(ConfigQuery query) {
|
||||
PageDTO<ConfigDTO> page = configApplicationService.getConfigList(query);
|
||||
return ResponseDTO.ok(page);
|
||||
}
|
||||
|
||||
@ -58,6 +63,8 @@ public class SysConfigController extends BaseController {
|
||||
* 换成用Enum
|
||||
*/
|
||||
@GetMapping(value = "/dict/{dictType}")
|
||||
@Operation(summary = "字典数据", description = "获取字典列表")
|
||||
@Parameter(name = "dictType", description = "字典对应类别")
|
||||
public ResponseDTO<List<DictionaryData>> dictType(@PathVariable String dictType) {
|
||||
List<DictionaryData> dictionaryData = MapCache.dictionaryCache().get(dictType);
|
||||
return ResponseDTO.ok(dictionaryData);
|
||||
@ -69,6 +76,7 @@ public class SysConfigController extends BaseController {
|
||||
*/
|
||||
@PreAuthorize("@permission.has('system:config:query')")
|
||||
@GetMapping(value = "/{configId}")
|
||||
@Operation(summary = "配置信息", description = "配置的详细信息")
|
||||
public ResponseDTO<ConfigDTO> getInfo(@NotNull @Positive @PathVariable Long configId) {
|
||||
ConfigDTO config = configApplicationService.getConfigInfo(configId);
|
||||
return ResponseDTO.ok(config);
|
||||
@ -80,6 +88,7 @@ public class SysConfigController extends BaseController {
|
||||
*/
|
||||
@PreAuthorize("@permission.has('system:config:edit')")
|
||||
@AccessLog(title = "参数管理", businessType = BusinessTypeEnum.MODIFY)
|
||||
@Operation(summary = "配置修改", description = "配置修改")
|
||||
@PutMapping
|
||||
public ResponseDTO<?> edit(@RequestBody ConfigUpdateCommand config) {
|
||||
configApplicationService.updateConfig(config);
|
||||
@ -89,6 +98,7 @@ public class SysConfigController extends BaseController {
|
||||
/**
|
||||
* 刷新参数缓存
|
||||
*/
|
||||
@Operation(summary = "刷新配置缓存")
|
||||
@PreAuthorize("@permission.has('system:config:remove')")
|
||||
@AccessLog(title = "参数管理", businessType = BusinessTypeEnum.CLEAN)
|
||||
@DeleteMapping("/refreshCache")
|
||||
|
||||
@ -142,23 +142,11 @@
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- swagger3-->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-models</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
@ -200,6 +188,12 @@
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- swagger注解 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import lombok.Data;
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
public class PageDTO {
|
||||
public class PageDTO<T> {
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
@ -18,20 +18,19 @@ public class PageDTO {
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
private List<?> rows;
|
||||
private List<T> rows;
|
||||
|
||||
public PageDTO(List<?> list) {
|
||||
public PageDTO(List<T> list) {
|
||||
this.rows = list;
|
||||
this.total = (long) list.size();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public PageDTO(Page page) {
|
||||
public PageDTO(Page<T> page) {
|
||||
this.rows = page.getRecords();
|
||||
this.total = page.getTotal();
|
||||
}
|
||||
|
||||
public PageDTO(List<?> list, Long count) {
|
||||
public PageDTO(List<T> list, Long count) {
|
||||
this.rows = list;
|
||||
this.total = count;
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.agileboot.domain.system.config.command;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Positive;
|
||||
@ -9,6 +10,7 @@ import lombok.Data;
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
@Schema
|
||||
public class ConfigUpdateCommand {
|
||||
|
||||
@NotNull
|
||||
|
||||
@ -6,6 +6,7 @@ import cn.hutool.json.JSONUtil;
|
||||
import com.agileboot.orm.common.enums.YesOrNoEnum;
|
||||
import com.agileboot.orm.common.util.BasicEnumUtil;
|
||||
import com.agileboot.orm.system.entity.SysConfigEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
@ -14,6 +15,7 @@ import lombok.Data;
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
@Schema(name = "ConfigDTO", description = "配置信息")
|
||||
public class ConfigDTO {
|
||||
|
||||
public ConfigDTO(SysConfigEntity entity) {
|
||||
|
||||
@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.agileboot.orm.common.query.AbstractPageQuery;
|
||||
import com.agileboot.orm.system.entity.SysConfigEntity;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@ -12,12 +13,16 @@ import lombok.NoArgsConstructor;
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "配置查询参数")
|
||||
public class ConfigQuery extends AbstractPageQuery {
|
||||
|
||||
@Schema(name = "配置名称")
|
||||
private String configName;
|
||||
|
||||
@Schema(name = "配置key")
|
||||
private String configKey;
|
||||
|
||||
@Schema(name = "是否允许更改配置")
|
||||
private Boolean isAllowChange;
|
||||
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.connection.RedisServerCommands;
|
||||
@ -76,12 +77,11 @@ public class MonitorApplicationService {
|
||||
public List<OnlineUser> getOnlineUserList(String userName, String ipaddr) {
|
||||
Collection<String> keys = redisUtil.keys(CacheKeyEnum.LOGIN_USER_KEY.key() + "*");
|
||||
|
||||
List<OnlineUser> allOnlineUsers = keys.stream().map(
|
||||
Stream<OnlineUser> onlineUserStream = keys.stream().map(
|
||||
o -> mapLoginUserToUserOnline(redisCacheService.loginUserCache.getObjectOnlyInCacheByKey(o)))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
.filter(Objects::nonNull);
|
||||
|
||||
List<OnlineUser> filteredOnlineUsers = allOnlineUsers.stream()
|
||||
List<OnlineUser> filteredOnlineUsers = onlineUserStream
|
||||
.filter(o ->
|
||||
StrUtil.isEmpty(userName) || userName.equals(o.getUserName())
|
||||
).filter( o ->
|
||||
|
||||
@ -104,6 +104,11 @@
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -22,9 +22,6 @@ public class ResourcesConfig implements WebMvcConfigurer {
|
||||
registry.addResourceHandler("/" + Constants.RESOURCE_PREFIX + "/**")
|
||||
.addResourceLocations("file:" + AgileBootConfig.getFileBaseDir() + "/");
|
||||
|
||||
/* swagger配置 */
|
||||
registry.addResourceHandler("/swagger-ui/**")
|
||||
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -143,7 +143,7 @@ public class SecurityConfig {
|
||||
.antMatchers("/swagger-ui.html").anonymous()
|
||||
.antMatchers("/swagger-resources/**").anonymous()
|
||||
.antMatchers("/webjars/**").anonymous()
|
||||
.antMatchers("/*/api-docs").anonymous()
|
||||
.antMatchers("/*/api-docs","/*/api-docs/swagger-config").anonymous()
|
||||
.antMatchers("/druid/**").anonymous()
|
||||
// 除上面外的所有请求全部需要鉴权认证
|
||||
.anyRequest().authenticated()
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
package com.agileboot.infrastructure.config;
|
||||
|
||||
import io.swagger.v3.oas.models.ExternalDocumentation;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springdoc.core.GroupedOpenApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
* SpringDoc API文档相关配置
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class SpringDocConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI agileBootApi() {
|
||||
return new OpenAPI()
|
||||
.info(new Info().title("Agileboot后台管理系统")
|
||||
.description("Agileboot API 演示")
|
||||
.version("v1.7.0")
|
||||
.license(new License().name("MIT 3.0").url("https://github.com/valarchie/AgileBoot-Back-End")))
|
||||
.externalDocs(new ExternalDocumentation()
|
||||
.description("Agileboot后台管理系统接口文档")
|
||||
.url("https://juejin.cn/column/7159946528827080734"));
|
||||
}
|
||||
|
||||
// @Bean
|
||||
// public GroupedOpenApi siteApi() {
|
||||
// return GroupedOpenApi.builder()
|
||||
// .group("site接口")
|
||||
// .displayName("dsdsd")
|
||||
// .pathsToMatch("/system/**")
|
||||
// .build();
|
||||
// }
|
||||
// @Bean
|
||||
// public GroupedOpenApi adminApi() {
|
||||
// return GroupedOpenApi.builder()
|
||||
// .group("admin接口")
|
||||
// .displayName("dsssdsd")
|
||||
// .pathsToMatch("/test/**")
|
||||
// .build();
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
@ -1,128 +0,0 @@
|
||||
package com.agileboot.infrastructure.config;
|
||||
|
||||
import com.agileboot.common.config.AgileBootConfig;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.models.auth.In;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.ApiKey;
|
||||
import springfox.documentation.service.AuthorizationScope;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.service.SecurityReference;
|
||||
import springfox.documentation.service.SecurityScheme;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
|
||||
/**
|
||||
* Swagger2的接口配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class SwaggerConfig {
|
||||
|
||||
/**
|
||||
* 系统基础配置
|
||||
*/
|
||||
@NonNull
|
||||
private AgileBootConfig agileBootConfig;
|
||||
|
||||
/**
|
||||
* 是否开启swagger
|
||||
*/
|
||||
@Value("${swagger.enabled}")
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* 设置请求的统一前缀
|
||||
*/
|
||||
@Value("${swagger.pathMapping}")
|
||||
private String pathMapping;
|
||||
|
||||
/**
|
||||
* 创建API
|
||||
*/
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
// 是否启用Swagger
|
||||
.enable(enabled)
|
||||
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
|
||||
.apiInfo(apiInfo())
|
||||
// 设置哪些接口暴露给Swagger展示
|
||||
.select()
|
||||
// 扫描所有有注解的api,用这种方式更灵活
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
// 扫描指定包中的swagger注解
|
||||
// .apis(RequestHandlerSelectors.basePackage("com.agileboot.project.tool.swagger"))
|
||||
// 扫描所有 .apis(RequestHandlerSelectors.any())
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
/* 设置安全模式,swagger可以设置访问token */
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts())
|
||||
.pathMapping(pathMapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全模式,这里指定token通过Authorization头请求头传递
|
||||
*/
|
||||
private List<SecurityScheme> securitySchemes() {
|
||||
List<SecurityScheme> apiKeyList = new ArrayList<>();
|
||||
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
|
||||
return apiKeyList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全上下文
|
||||
*/
|
||||
private List<SecurityContext> securityContexts() {
|
||||
List<SecurityContext> securityContexts = new ArrayList<>();
|
||||
securityContexts.add(
|
||||
SecurityContext.builder()
|
||||
.securityReferences(defaultAuth())
|
||||
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
|
||||
.build());
|
||||
return securityContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的安全上引用
|
||||
*/
|
||||
private List<SecurityReference> defaultAuth() {
|
||||
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||||
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||
authorizationScopes[0] = authorizationScope;
|
||||
List<SecurityReference> securityReferences = new ArrayList<>();
|
||||
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
|
||||
return securityReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加摘要信息
|
||||
*/
|
||||
private ApiInfo apiInfo() {
|
||||
// 用ApiInfoBuilder进行定制
|
||||
return new ApiInfoBuilder()
|
||||
// 设置标题
|
||||
.title("标题:AgileBoot管理系统_接口文档")
|
||||
// 描述
|
||||
.description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
|
||||
// 作者信息
|
||||
.contact(new Contact(agileBootConfig.getName(), null, null))
|
||||
// 版本
|
||||
.version("版本号:" + agileBootConfig.getVersion())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -45,7 +45,7 @@ public class CodeGenerator {
|
||||
public static void main(String[] args) {
|
||||
|
||||
CodeGenerator generator = CodeGenerator.builder()
|
||||
.databaseUrl("jdbc:mysql://localhost:33067/agileboot4")
|
||||
.databaseUrl("jdbc:mysql://localhost:33067/agileboot")
|
||||
.username("root")
|
||||
.password("12345")
|
||||
.author("valarchie")
|
||||
|
||||
@ -113,12 +113,26 @@ pagehelper:
|
||||
supportMethodsArguments: true
|
||||
params: count=countSql
|
||||
|
||||
# Swagger配置
|
||||
swagger:
|
||||
# 是否开启swagger
|
||||
enabled: true
|
||||
# 请求前缀
|
||||
pathMapping: /dev-api
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
# 修改Swagger UI路径
|
||||
path: /swagger-ui.html
|
||||
# 开启Swagger UI界面
|
||||
enabled: true
|
||||
url: /dev-api/v3/api-docs
|
||||
config-url: /dev-api/v3/api-docs/swagger-config
|
||||
# disable-swagger-default-url: true
|
||||
api-docs:
|
||||
# 修改api-docs路径
|
||||
path: /v3/api-docs
|
||||
# 开启api-docs
|
||||
enabled: true
|
||||
groups:
|
||||
enabled: true
|
||||
# 配置需要生成接口文档的扫描包
|
||||
packages-to-scan: com.agileboot
|
||||
# 配置需要生成接口文档的接口路径
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
34
pom.xml
34
pom.xml
@ -38,9 +38,10 @@
|
||||
<mockito.version>1.10.19</mockito.version>
|
||||
<common-validator.version>1.7</common-validator.version>
|
||||
<it.ozimov.version>0.7.3</it.ozimov.version>
|
||||
<io.swagger.version>1.6.2</io.swagger.version>
|
||||
<io.swagger.version>1.6.8</io.swagger.version>
|
||||
<org.lionsoul.version>2.6.5</org.lionsoul.version>
|
||||
<com.google.guava.version>31.0.1-jre</com.google.guava.version>
|
||||
<springdoc-openapi-ui.version>1.6.14</springdoc-openapi-ui.version>
|
||||
</properties>
|
||||
|
||||
<!-- 依赖声明 -->
|
||||
@ -105,19 +106,6 @@
|
||||
<version>${oshi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Swagger3依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-models</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- io常用工具类 -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
@ -244,12 +232,6 @@
|
||||
<version>${it.ozimov.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-models</artifactId>
|
||||
<version>${io.swagger.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 本地ip库 -->
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
@ -271,6 +253,18 @@
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>${springdoc-openapi-ui.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- swagger注解 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${io.swagger.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user