初始化提交
This commit is contained in:
19
src/main/java/com/yolo/keyborad/MyApplication.java
Normal file
19
src/main/java/com/yolo/keyborad/MyApplication.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.yolo.keyborad;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.yolo.keyborad.mapper")
|
||||
public class MyApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MyApplication.class, args);
|
||||
log.info("info message");
|
||||
log.error("error message");
|
||||
}
|
||||
|
||||
}
|
||||
32
src/main/java/com/yolo/keyborad/annotation/AuthCheck.java
Normal file
32
src/main/java/com/yolo/keyborad/annotation/AuthCheck.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.yolo.keyborad.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 权限校验
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AuthCheck {
|
||||
|
||||
/**
|
||||
* 有任何一个角色
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String[] anyRole() default "";
|
||||
|
||||
/**
|
||||
* 必须有某个角色
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String mustRole() default "";
|
||||
|
||||
}
|
||||
|
||||
56
src/main/java/com/yolo/keyborad/aop/LogInterceptor.java
Normal file
56
src/main/java/com/yolo/keyborad/aop/LogInterceptor.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package com.yolo.keyborad.aop;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 请求响应日志 AOP
|
||||
*
|
||||
* @author yupi
|
||||
**/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LogInterceptor {
|
||||
|
||||
/**
|
||||
* 执行拦截
|
||||
*/
|
||||
@Around("execution(* com.yupi.project.controller.*.*(..))")
|
||||
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
|
||||
// 计时
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start();
|
||||
// 获取请求路径
|
||||
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
|
||||
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
// 生成请求唯一 id
|
||||
String requestId = UUID.randomUUID().toString();
|
||||
String url = httpServletRequest.getRequestURI();
|
||||
// 获取请求参数
|
||||
Object[] args = point.getArgs();
|
||||
String reqParam = "[" + StringUtils.join(args, ", ") + "]";
|
||||
// 输出请求日志
|
||||
log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
|
||||
httpServletRequest.getRemoteHost(), reqParam);
|
||||
// 执行原方法
|
||||
Object result = point.proceed();
|
||||
// 输出响应日志
|
||||
stopWatch.stop();
|
||||
long totalTimeMillis = stopWatch.getTotalTimeMillis();
|
||||
log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
35
src/main/java/com/yolo/keyborad/common/BaseResponse.java
Normal file
35
src/main/java/com/yolo/keyborad/common/BaseResponse.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.yolo.keyborad.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 通用返回类
|
||||
*
|
||||
* @param <T>
|
||||
* @author yupi
|
||||
*/
|
||||
@Data
|
||||
public class BaseResponse<T> implements Serializable {
|
||||
|
||||
private int code;
|
||||
|
||||
private T data;
|
||||
|
||||
private String message;
|
||||
|
||||
public BaseResponse(int code, T data, String message) {
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BaseResponse(int code, T data) {
|
||||
this(code, data, "");
|
||||
}
|
||||
|
||||
public BaseResponse(ErrorCode errorCode) {
|
||||
this(errorCode.getCode(), null, errorCode.getMessage());
|
||||
}
|
||||
}
|
||||
20
src/main/java/com/yolo/keyborad/common/DeleteRequest.java
Normal file
20
src/main/java/com/yolo/keyborad/common/DeleteRequest.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.yolo.keyborad.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 删除请求
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Data
|
||||
public class DeleteRequest implements Serializable {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
42
src/main/java/com/yolo/keyborad/common/ErrorCode.java
Normal file
42
src/main/java/com/yolo/keyborad/common/ErrorCode.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.yolo.keyborad.common;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public enum ErrorCode {
|
||||
|
||||
SUCCESS(0, "ok"),
|
||||
PARAMS_ERROR(40000, "请求参数错误"),
|
||||
NOT_LOGIN_ERROR(40100, "未登录"),
|
||||
NO_AUTH_ERROR(40101, "无权限"),
|
||||
NOT_FOUND_ERROR(40400, "请求数据不存在"),
|
||||
FORBIDDEN_ERROR(40300, "禁止访问"),
|
||||
SYSTEM_ERROR(50000, "系统内部异常"),
|
||||
OPERATION_ERROR(50001, "操作失败");
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private final int code;
|
||||
|
||||
/**
|
||||
* 信息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
ErrorCode(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
33
src/main/java/com/yolo/keyborad/common/PageRequest.java
Normal file
33
src/main/java/com/yolo/keyborad/common/PageRequest.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package com.yolo.keyborad.common;
|
||||
|
||||
import com.yolo.keyborad.constant.CommonConstant;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分页请求
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Data
|
||||
public class PageRequest {
|
||||
|
||||
/**
|
||||
* 当前页号
|
||||
*/
|
||||
private long current = 1;
|
||||
|
||||
/**
|
||||
* 页面大小
|
||||
*/
|
||||
private long pageSize = 10;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
private String sortField;
|
||||
|
||||
/**
|
||||
* 排序顺序(默认升序)
|
||||
*/
|
||||
private String sortOrder = CommonConstant.SORT_ORDER_ASC;
|
||||
}
|
||||
51
src/main/java/com/yolo/keyborad/common/ResultUtils.java
Normal file
51
src/main/java/com/yolo/keyborad/common/ResultUtils.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.yolo.keyborad.common;
|
||||
|
||||
/**
|
||||
* 返回工具类
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public class ResultUtils {
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*
|
||||
* @param data
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T> BaseResponse<T> success(T data) {
|
||||
return new BaseResponse<>(0, data, "ok");
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*
|
||||
* @param errorCode
|
||||
* @return
|
||||
*/
|
||||
public static BaseResponse error(ErrorCode errorCode) {
|
||||
return new BaseResponse<>(errorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*
|
||||
* @param code
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public static BaseResponse error(int code, String message) {
|
||||
return new BaseResponse(code, null, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*
|
||||
* @param errorCode
|
||||
* @return
|
||||
*/
|
||||
public static BaseResponse error(ErrorCode errorCode, String message) {
|
||||
return new BaseResponse(errorCode.getCode(), null, message);
|
||||
}
|
||||
}
|
||||
27
src/main/java/com/yolo/keyborad/config/CorsConfig.java
Normal file
27
src/main/java/com/yolo/keyborad/config/CorsConfig.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.yolo.keyborad.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 全局跨域配置
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
// 覆盖所有请求
|
||||
registry.addMapping("/**")
|
||||
// 允许发送 Cookie
|
||||
.allowCredentials(true)
|
||||
// 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.exposedHeaders("*");
|
||||
}
|
||||
}
|
||||
38
src/main/java/com/yolo/keyborad/config/Knife4jConfig.java
Normal file
38
src/main/java/com/yolo/keyborad/config/Knife4jConfig.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package com.yolo.keyborad.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
/**
|
||||
* Knife4j 接口文档配置
|
||||
* https://doc.xiaominfo.com/knife4j/documentation/get_start.html
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
@Profile("dev")
|
||||
public class Knife4jConfig {
|
||||
|
||||
@Bean
|
||||
public Docket defaultApi2() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(new ApiInfoBuilder()
|
||||
.title("project-backend")
|
||||
.description("project-backend")
|
||||
.version("1.0")
|
||||
.build())
|
||||
.select()
|
||||
// 指定 Controller 扫描包路径
|
||||
.apis(RequestHandlerSelectors.basePackage("com.yupi.project.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.yolo.keyborad.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis Plus 配置
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Configuration
|
||||
@MapperScan("com.yupi.project.mapper")
|
||||
public class MyBatisPlusConfig {
|
||||
|
||||
/**
|
||||
* 拦截器配置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/yolo/keyborad/constant/CommonConstant.java
Normal file
19
src/main/java/com/yolo/keyborad/constant/CommonConstant.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.yolo.keyborad.constant;
|
||||
|
||||
/**
|
||||
* 通用常量
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public interface CommonConstant {
|
||||
|
||||
/**
|
||||
* 升序
|
||||
*/
|
||||
String SORT_ORDER_ASC = "ascend";
|
||||
|
||||
/**
|
||||
* 降序
|
||||
*/
|
||||
String SORT_ORDER_DESC = " descend";
|
||||
}
|
||||
33
src/main/java/com/yolo/keyborad/constant/UserConstant.java
Normal file
33
src/main/java/com/yolo/keyborad/constant/UserConstant.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package com.yolo.keyborad.constant;
|
||||
|
||||
/**
|
||||
* 用户常量
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public interface UserConstant {
|
||||
|
||||
/**
|
||||
* 用户登录态键
|
||||
*/
|
||||
String USER_LOGIN_STATE = "userLoginState";
|
||||
|
||||
/**
|
||||
* 系统用户 id(虚拟用户)
|
||||
*/
|
||||
long SYSTEM_USER_ID = 0;
|
||||
|
||||
// region 权限
|
||||
|
||||
/**
|
||||
* 默认权限
|
||||
*/
|
||||
String DEFAULT_ROLE = "user";
|
||||
|
||||
/**
|
||||
* 管理员权限
|
||||
*/
|
||||
String ADMIN_ROLE = "admin";
|
||||
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.yolo.keyborad.exception;
|
||||
|
||||
import com.yolo.keyborad.common.ErrorCode;
|
||||
|
||||
/**
|
||||
* 自定义异常类
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private final int code;
|
||||
|
||||
public BusinessException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public BusinessException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(ErrorCode errorCode, String message) {
|
||||
super(message);
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.yolo.keyborad.exception;
|
||||
|
||||
import com.yolo.keyborad.common.BaseResponse;
|
||||
import com.yolo.keyborad.common.ErrorCode;
|
||||
import com.yolo.keyborad.common.ResultUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public BaseResponse<?> businessExceptionHandler(BusinessException e) {
|
||||
log.error("businessException: " + e.getMessage(), e);
|
||||
return ResultUtils.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {
|
||||
log.error("runtimeException", e);
|
||||
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.yolo.keyborad.model.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 帖子性别枚举
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public enum PostGenderEnum {
|
||||
|
||||
MALE("男", 0),
|
||||
FEMALE("女", 1);
|
||||
|
||||
private final String text;
|
||||
|
||||
private final int value;
|
||||
|
||||
PostGenderEnum(String text, int value) {
|
||||
this.text = text;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static List<Integer> getValues() {
|
||||
return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.yolo.keyborad.model.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 帖子审核状态枚举
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public enum PostReviewStatusEnum {
|
||||
|
||||
REVIEWING("待审核", 0),
|
||||
PASS("通过", 1),
|
||||
REJECT("拒绝", 2);
|
||||
|
||||
private final String text;
|
||||
|
||||
private final int value;
|
||||
|
||||
PostReviewStatusEnum(String text, int value) {
|
||||
this.text = text;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static List<Integer> getValues() {
|
||||
return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
6
src/main/resources/application-dev.yml
Normal file
6
src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://localhost:5432/postgres
|
||||
username: root
|
||||
password: 123asd
|
||||
6
src/main/resources/application-prod.yml
Normal file
6
src/main/resources/application-prod.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/my_db
|
||||
username: root
|
||||
password: 123456
|
||||
36
src/main/resources/application.yml
Normal file
36
src/main/resources/application.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
spring:
|
||||
application:
|
||||
name: springboot-init
|
||||
profiles:
|
||||
active: dev
|
||||
# DataSource Config
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://localhost:5432/postgres
|
||||
username: root
|
||||
password: 123asd
|
||||
mvc:
|
||||
pathmatch:
|
||||
matching-strategy: ANT_PATH_MATCHER
|
||||
# session 失效时间(分钟)
|
||||
session:
|
||||
timeout: 86400
|
||||
store-type: redis
|
||||
# redis 配置
|
||||
redis:
|
||||
port: 6379
|
||||
host: localhost
|
||||
database: 0
|
||||
server:
|
||||
port: 7529
|
||||
servlet:
|
||||
context-path: /api
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: false
|
||||
log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
|
||||
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
|
||||
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
|
||||
1
src/main/resources/banner.txt
Normal file
1
src/main/resources/banner.txt
Normal file
@@ -0,0 +1 @@
|
||||
我的项目 by 程序员鱼皮 https://github.com/liyupi
|
||||
Reference in New Issue
Block a user