mirror of
https://gitee.com/anji-plus/report.git
synced 2026-04-13 10:38:34 +08:00
报表分享
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package com.anjiplus.template.gaea.business.enums;
|
||||
public enum DeleteFlagEnum {
|
||||
DELETED(1,"已删除"),
|
||||
UNDELETED(0,"未删除"),
|
||||
;
|
||||
|
||||
private int codeValue;
|
||||
private String codeDesc;
|
||||
|
||||
private DeleteFlagEnum(int codeValue, String codeDesc) {
|
||||
this.codeValue = codeValue;
|
||||
this.codeDesc = codeDesc;
|
||||
}
|
||||
|
||||
public int getCodeValue(){ return this.codeValue;}
|
||||
|
||||
public String getCodeDesc(){ return this.codeDesc;}
|
||||
|
||||
//根据codeValue获取枚举
|
||||
public static DeleteFlagEnum parseFromCodeValue(int codeValue){
|
||||
for (DeleteFlagEnum e : DeleteFlagEnum.values()){
|
||||
if(e.codeValue == codeValue){ return e;}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//根据codeValue获取描述
|
||||
public static String getCodeDescByCodeValue(int codeValue){
|
||||
DeleteFlagEnum enumItem = parseFromCodeValue(codeValue);
|
||||
return enumItem == null ? "" : enumItem.getCodeDesc();
|
||||
}
|
||||
|
||||
//验证codeValue是否有效
|
||||
public static boolean validateCodeValue(int codeValue){ return parseFromCodeValue(codeValue)!=null;}
|
||||
|
||||
//列出所有值字符串
|
||||
public static String getString(){
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (DeleteFlagEnum e : DeleteFlagEnum.values()){
|
||||
buffer.append(e.codeValue).append("--").append(e.getCodeDesc()).append(", ");
|
||||
}
|
||||
buffer.deleteCharAt(buffer.lastIndexOf(","));
|
||||
return buffer.toString().trim();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.anjiplus.template.gaea.business.enums;
|
||||
public enum EnableFlagEnum {
|
||||
ENABLE(1,"启用"),
|
||||
DISABLE(0,"禁用"),
|
||||
;
|
||||
|
||||
private int codeValue;
|
||||
private String codeDesc;
|
||||
|
||||
private EnableFlagEnum(int codeValue, String codeDesc) {
|
||||
this.codeValue = codeValue;
|
||||
this.codeDesc = codeDesc;
|
||||
}
|
||||
|
||||
public int getCodeValue(){ return this.codeValue;}
|
||||
|
||||
public String getCodeDesc(){ return this.codeDesc;}
|
||||
|
||||
//根据codeValue获取枚举
|
||||
public static EnableFlagEnum parseFromCodeValue(int codeValue){
|
||||
for (EnableFlagEnum e : EnableFlagEnum.values()){
|
||||
if(e.codeValue == codeValue){ return e;}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//根据codeValue获取描述
|
||||
public static String getCodeDescByCodeBalue(int codeValue){
|
||||
EnableFlagEnum enumItem = parseFromCodeValue(codeValue);
|
||||
return enumItem == null ? "" : enumItem.getCodeDesc();
|
||||
}
|
||||
|
||||
//验证codeValue是否有效
|
||||
public static boolean validateCodeValue(int codeValue){ return parseFromCodeValue(codeValue)!=null;}
|
||||
|
||||
//列出所有值字符串
|
||||
public static String getString(){
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
for (EnableFlagEnum e : EnableFlagEnum.values()){
|
||||
buffer.append(e.codeValue).append("--").append(e.getCodeDesc()).append(", ");
|
||||
}
|
||||
buffer.deleteCharAt(buffer.lastIndexOf(","));
|
||||
return buffer.toString().trim();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.anjiplus.template.gaea.business.modules.reportshare.controller;
|
||||
|
||||
import com.anji.plus.gaea.annotation.AccessKey;
|
||||
import com.anji.plus.gaea.annotation.Permission;
|
||||
import com.anji.plus.gaea.annotation.log.GaeaAuditLog;
|
||||
import com.anji.plus.gaea.bean.ResponseBean;
|
||||
import com.anji.plus.gaea.curd.controller.GaeaBaseController;
|
||||
import com.anji.plus.gaea.curd.service.GaeaBaseService;
|
||||
@@ -14,16 +15,14 @@ import com.anjiplus.template.gaea.business.modules.reportshare.dao.entity.Report
|
||||
import com.anjiplus.template.gaea.business.modules.reportshare.service.ReportShareService;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.RestController;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @desc 报表分享 controller
|
||||
* @author Raod
|
||||
* @date 2021-08-18 13:37:26.663
|
||||
**/
|
||||
* @author Raod
|
||||
* @desc 报表分享 controller
|
||||
* @date 2021-08-18 13:37:26.663
|
||||
**/
|
||||
@RestController
|
||||
@Api(tags = "报表分享管理")
|
||||
@RequestMapping("/reportShare")
|
||||
@@ -63,4 +62,23 @@ public class ReportShareController extends GaeaBaseController<ReportShareParam,
|
||||
return responseBean;
|
||||
}
|
||||
|
||||
@GetMapping({"/detailByCode"})
|
||||
@Permission(code = "detail", name = "明细")
|
||||
public ResponseBean detailByCode(@RequestParam("shareCode") String shareCode) {
|
||||
return ResponseBean.builder().data(reportShareService.detailByCode(shareCode)).build();
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@Permission(
|
||||
code = "insert",
|
||||
name = "新增"
|
||||
)
|
||||
@GaeaAuditLog(
|
||||
pageTitle = "新增"
|
||||
)
|
||||
@Override
|
||||
public ResponseBean insert(@Validated @RequestBody ReportShareDto dto) {
|
||||
return ResponseBean.builder().data(reportShareService.insertShare(dto)).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import java.io.Serializable;
|
||||
import com.anji.plus.gaea.curd.dto.GaeaBaseDTO;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -21,6 +24,7 @@ public class ReportShareDto extends GaeaBaseDTO implements Serializable {
|
||||
|
||||
/** 分享有效期类型,DIC_NAME=SHARE_VAILD */
|
||||
@ApiModelProperty(value = "分享有效期类型,DIC_NAME=SHARE_VAILD")
|
||||
@NotNull(message = "6002")
|
||||
private Integer shareValidType;
|
||||
|
||||
/** 分享有效期 */
|
||||
@@ -29,10 +33,12 @@ public class ReportShareDto extends GaeaBaseDTO implements Serializable {
|
||||
|
||||
/** 分享url */
|
||||
@ApiModelProperty(value = "分享url")
|
||||
@NotEmpty(message = "6002")
|
||||
private String shareUrl;
|
||||
|
||||
/** 报表编码 */
|
||||
@ApiModelProperty(value = "报表编码")
|
||||
@NotEmpty(message = "6002")
|
||||
private String reportCode;
|
||||
|
||||
/** 0--已禁用 1--已启用 DIC_NAME=ENABLE_FLAG */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.anjiplus.template.gaea.business.modules.reportshare.service;
|
||||
|
||||
import com.anji.plus.gaea.curd.service.GaeaBaseService;
|
||||
import com.anjiplus.template.gaea.business.modules.reportshare.controller.dto.ReportShareDto;
|
||||
import com.anjiplus.template.gaea.business.modules.reportshare.controller.param.ReportShareParam;
|
||||
import com.anjiplus.template.gaea.business.modules.reportshare.dao.entity.ReportShare;
|
||||
|
||||
@@ -20,4 +21,7 @@ public interface ReportShareService extends GaeaBaseService<ReportShareParam, Re
|
||||
*/
|
||||
ReportShare getDetail(Long id);
|
||||
|
||||
ReportShare insertShare(ReportShareDto dto);
|
||||
|
||||
ReportShare detailByCode(String shareCode);
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@ package com.anjiplus.template.gaea.business.modules.reportshare.service.impl;
|
||||
import com.anji.plus.gaea.constant.BaseOperationEnum;
|
||||
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
|
||||
import com.anji.plus.gaea.exception.BusinessException;
|
||||
import com.anjiplus.template.gaea.business.enums.EnableFlagEnum;
|
||||
import com.anjiplus.template.gaea.business.modules.reportshare.controller.dto.ReportShareDto;
|
||||
import com.anjiplus.template.gaea.business.modules.reportshare.dao.ReportShareMapper;
|
||||
import com.anjiplus.template.gaea.business.modules.reportshare.dao.entity.ReportShare;
|
||||
import com.anjiplus.template.gaea.business.modules.reportshare.service.ReportShareService;
|
||||
import com.anjiplus.template.gaea.business.util.DateUtil;
|
||||
import com.anjiplus.template.gaea.business.util.MD5Util;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,13 @@ import java.util.UUID;
|
||||
@Service
|
||||
public class ReportShareServiceImpl implements ReportShareService {
|
||||
|
||||
/**
|
||||
* 默认跳转路由为aj的页面
|
||||
*/
|
||||
private static final String SHARE_FLAG = "#/aj/";
|
||||
|
||||
private static final String SHARE_URL = "#";
|
||||
|
||||
@Autowired
|
||||
private ReportShareMapper reportShareMapper;
|
||||
|
||||
@@ -38,6 +47,22 @@ public class ReportShareServiceImpl implements ReportShareService {
|
||||
return reportShare;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReportShare insertShare(ReportShareDto dto) {
|
||||
ReportShare entity = new ReportShare();
|
||||
BeanUtils.copyProperties(dto, entity);
|
||||
insert(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReportShare detailByCode(String shareCode) {
|
||||
LambdaQueryWrapper<ReportShare> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(ReportShare::getShareCode, shareCode);
|
||||
wrapper.eq(ReportShare::getEnableFlag, EnableFlagEnum.ENABLE.getCodeDesc());
|
||||
return selectOne(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBeforeOperation(ReportShare entity, BaseOperationEnum operationEnum) throws BusinessException {
|
||||
switch (operationEnum) {
|
||||
@@ -48,9 +73,11 @@ public class ReportShareServiceImpl implements ReportShareService {
|
||||
//http://127.0.0.1:9095/reportDashboard/getData
|
||||
String shareCode = UUID.randomUUID().toString();
|
||||
entity.setShareCode(shareCode);
|
||||
if (StringUtils.isNotBlank(entity.getShareUrl())) {
|
||||
if (entity.getShareUrl().contains(SHARE_URL)) {
|
||||
String prefix = entity.getShareUrl().substring(0, entity.getShareUrl().indexOf("#"));
|
||||
entity.setShareUrl(prefix + "#/bigscreen/viewer?reportCode=" + entity.getReportCode());
|
||||
entity.setShareUrl(prefix + SHARE_FLAG + shareCode);
|
||||
} else {
|
||||
entity.setShareUrl(entity.getShareUrl() + SHARE_FLAG + shareCode);
|
||||
}
|
||||
entity.setShareValidTime(DateUtil.getFutureDateTmdHms(entity.getShareValidType()));
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
*Copyright © 2018 anji-plus
|
||||
*安吉加加信息技术有限公司
|
||||
*http://www.anji-plus.com
|
||||
*All rights reserved.
|
||||
*/
|
||||
package com.anjiplus.template.gaea.business.util;
|
||||
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Base64;
|
||||
|
||||
|
||||
public class AESUtil {
|
||||
//算法
|
||||
private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";
|
||||
|
||||
private static final String AES_KEY = "AnjiPLUSAjReport";
|
||||
|
||||
|
||||
/**
|
||||
* 获取随机key
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getKey() {
|
||||
return AES_KEY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将byte[]转为各种进制的字符串
|
||||
*
|
||||
* @param bytes byte[]
|
||||
* @param radix 可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,超出范围后变为10进制
|
||||
* @return 转换后的字符串
|
||||
*/
|
||||
public static String binary(byte[] bytes, int radix) {
|
||||
return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数
|
||||
}
|
||||
|
||||
/**
|
||||
* base 64 encode
|
||||
*
|
||||
* @param bytes 待编码的byte[]
|
||||
* @return 编码后的base 64 code
|
||||
*/
|
||||
public static String base64Encode(byte[] bytes) {
|
||||
//return Base64.encodeBase64String(bytes);
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* base 64 decode
|
||||
*
|
||||
* @param base64Code 待解码的base 64 code
|
||||
* @return 解码后的byte[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public static byte[] base64Decode(String base64Code) throws Exception {
|
||||
Base64.Decoder decoder = Base64.getDecoder();
|
||||
return StringUtils.isEmpty(base64Code) ? null : decoder.decode(base64Code);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AES加密
|
||||
*
|
||||
* @param content 待加密的内容
|
||||
* @param encryptKey 加密密钥
|
||||
* @return 加密后的byte[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
|
||||
KeyGenerator kgen = KeyGenerator.getInstance("AES");
|
||||
kgen.init(128);
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
|
||||
|
||||
return cipher.doFinal(content.getBytes("utf-8"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AES加密为base 64 code
|
||||
*
|
||||
* @param content 待加密的内容
|
||||
* @param encryptKey 加密密钥
|
||||
* @return 加密后的base 64 code
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String aesEncrypt(String content, String encryptKey) throws Exception {
|
||||
if (StringUtils.isBlank(encryptKey)) {
|
||||
return content;
|
||||
}
|
||||
return base64Encode(aesEncryptToBytes(content, encryptKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*
|
||||
* @param encryptBytes 待解密的byte[]
|
||||
* @param decryptKey 解密密钥
|
||||
* @return 解密后的String
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
|
||||
KeyGenerator kgen = KeyGenerator.getInstance("AES");
|
||||
kgen.init(128);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));
|
||||
byte[] decryptBytes = cipher.doFinal(encryptBytes);
|
||||
return new String(decryptBytes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将base 64 code AES解密
|
||||
*
|
||||
* @param encryptStr 待解密的base 64 code
|
||||
* @param decryptKey 解密密钥
|
||||
* @return 解密后的string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
|
||||
if (StringUtils.isBlank(decryptKey)) {
|
||||
return encryptStr;
|
||||
}
|
||||
return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
String randomString = getKey();
|
||||
String content = "report";
|
||||
System.out.println("加密前:" + content);
|
||||
System.out.println("加密密钥和解密密钥:" + randomString);
|
||||
String encrypt = aesEncrypt(content, randomString);
|
||||
System.out.println("加密后:" + encrypt);
|
||||
String decrypt = aesDecrypt(encrypt, randomString);
|
||||
System.out.println("解密后:" + decrypt);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -99,6 +99,7 @@
|
||||
<Share
|
||||
:visib="visibleForShareDialog"
|
||||
:reportCode="reportCodeForShareDialog"
|
||||
:reportName="reportNameForShareDialog"
|
||||
@handleClose="visibleForShareDialog = false"
|
||||
/>
|
||||
</div>
|
||||
@@ -127,7 +128,8 @@ export default {
|
||||
},
|
||||
// 分享
|
||||
visibleForShareDialog: false,
|
||||
reportCodeForShareDialog: ""
|
||||
reportCodeForShareDialog: "",
|
||||
reportNameForShareDialog: ""
|
||||
};
|
||||
},
|
||||
mounted() {},
|
||||
@@ -172,6 +174,7 @@ export default {
|
||||
// 分享
|
||||
share(val) {
|
||||
this.reportCodeForShareDialog = val.reportCode;
|
||||
this.reportNameForShareDialog = val.reportName;
|
||||
this.visibleForShareDialog = true;
|
||||
},
|
||||
openDesign(val) {
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
<template>
|
||||
<el-dialog class="tree_dialog" title="报表分享" width="60%" :close-on-click-modal="false" center :visible.sync="visib" :before-close="closeDialog">
|
||||
<el-dialog class="tree_dialog" :title="title" width="60%" :close-on-click-modal="false" center :visible.sync="visib" :before-close="closeDialog">
|
||||
<el-form ref="userForm" :model="dialogForm" :rules="rules" size="small" label-width="100px">
|
||||
<el-row :gutter="10">
|
||||
<el-col :xs="24" :sm="20" :md="6" :lg="6" :xl="6">
|
||||
<el-form-item label="有效期" prop="shareValidType">
|
||||
<el-select v-model.trim="dialogForm.shareValidType" placeholder="请选择" clearable @change="selectChange">
|
||||
<el-option v-for="item in shareValidTypeOptions" :key="item.id" :label="item.text" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<el-button type="primary" plain @click="createShare">创建链接</el-button>
|
||||
{{reportShareUrl}}
|
||||
<div slot="footer" style="text-align: center">
|
||||
{{reportCode}}
|
||||
<el-button type="primary" plain @click="saveReportShare">保存</el-button>
|
||||
|
||||
<!-- <el-button type="primary" plain @click="saveReportShare">保存</el-button>-->
|
||||
<el-button type="danger" plain @click="closeDialog">取消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { reportShareAdd } from '@/api/reportShare'
|
||||
import { getDictList } from '@/api/dict-data' // 获取数据字典
|
||||
import Dictionary from '@/components/Dictionary/index'
|
||||
export default {
|
||||
components: { Dictionary },
|
||||
props: {
|
||||
visib: {
|
||||
required: true,
|
||||
@@ -22,20 +40,65 @@ export default {
|
||||
return ''
|
||||
},
|
||||
},
|
||||
reportName: {
|
||||
required: true,
|
||||
type: String,
|
||||
default: () => {
|
||||
return ''
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
title: '报表分享-' + this.reportName + '【' +this.reportCode + '】',
|
||||
reportShareUrl:'',
|
||||
shareValidTypeOptions: [], // 有效期类型
|
||||
dialogForm: {
|
||||
shareValidType: 0,
|
||||
reportCode: '',
|
||||
shareUrl: '',
|
||||
shareCode: '',
|
||||
},
|
||||
rules: {
|
||||
shareValidType: [
|
||||
{required: true, message: '有效期必选', trigger: 'change'},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
visib(val) {
|
||||
if (val) {
|
||||
// 弹窗弹出时需要执行的逻辑
|
||||
this.getSystem()
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
selectChange(val) {
|
||||
this.dialogForm.shareValidType = val
|
||||
},
|
||||
// 获取数据字典
|
||||
async getSystem() {
|
||||
const { code, data } = await getDictList('SHARE_VAILD')
|
||||
if (code != '200') return
|
||||
this.shareValidTypeOptions = data
|
||||
this.dialogForm.shareValidType = this.shareValidTypeOptions[0].id
|
||||
},
|
||||
async createShare() {
|
||||
this.dialogForm.reportCode = this.reportCode
|
||||
this.dialogForm.shareUrl = window.location.href
|
||||
console.log(this.dialogForm)
|
||||
const {code, data} = await reportShareAdd(this.dialogForm)
|
||||
if (code != '200') return
|
||||
console.log(data)
|
||||
this.$message({
|
||||
message: '创建链接成功!',
|
||||
type: 'success',
|
||||
})
|
||||
this.reportShareUrl = data.shareUrl
|
||||
},
|
||||
|
||||
async saveReportShare() {
|
||||
var params = {
|
||||
|
||||
Reference in New Issue
Block a user