1、ddos配置添加user_region字段;新增voip和流媒体业务;

2、ddos日志查询接口添加isBlock查询条件;
3、新增NTC VoIP、流媒体、文件摘要原始日志查询接口;
4、NTC BGP日志添加type、asNumber、route三个字段;
This commit is contained in:
zhangdongxu
2018-10-10 15:16:46 +08:00
parent 5564d2c3c7
commit ca4799297a
14 changed files with 557 additions and 42 deletions

View File

@@ -0,0 +1,71 @@
package com.nis.domain.restful;
import com.nis.domain.LogEntity;
import com.wordnik.swagger.annotations.ApiModelProperty;
/**
* @ClassName:MmFileDigestLog
* @Description:TODO(这里用一句话描述这个类的作用)
* @author zdx
* @date 2018年10月9日 下午6:55:48
* @version V1.0
*/
public class MmFileDigestLog extends LogEntity<MmFileDigestLog> {
private static final long serialVersionUID = -6491462762322051638L;
@ApiModelProperty(value="节目ID", required=true)
protected String pid;
@ApiModelProperty(value="节目访问地址", required=true)
protected String url;
@ApiModelProperty(value="封堵现场片段路径", required=true)
protected String logUri;
@ApiModelProperty(value="入口页面", required=true)
protected String referer;
@ApiModelProperty(value="有害级别", required=true)
protected Integer level;
@ApiModelProperty(value="封堵类型", required=true)
protected Integer fdType;
@ApiModelProperty(value="协议", required=true)
protected String protocol;
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLogUri() {
return logUri;
}
public void setLogUri(String logUri) {
this.logUri = logUri;
}
public String getReferer() {
return referer;
}
public void setReferer(String referer) {
this.referer = referer;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
public Integer getFdType() {
return fdType;
}
public void setFdType(Integer fdType) {
this.fdType = fdType;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
}

View File

@@ -1,6 +1,7 @@
package com.nis.domain.restful;
import com.nis.domain.LogEntity;
import com.wordnik.swagger.annotations.ApiModelProperty;
/**
*
@@ -13,6 +14,30 @@ import com.nis.domain.LogEntity;
public class NtcBgpLog extends LogEntity<NtcBgpLog> {
private static final long serialVersionUID = -990806269407561021L;
@ApiModelProperty(value="BGP消息类型", required=true)
protected Integer type;
@ApiModelProperty(value="自治系统号", required=true)
protected String asNumber;
@ApiModelProperty(value="路由信息", required=true)
protected String route;
public void setType(Integer type) {
this.type = type;
}
public Integer getType() {
return type;
}
public void setAsNumber(String asNumber) {
this.asNumber = asNumber;
}
public String getAsNumber() {
return asNumber;
}
public void setRoute(String route) {
this.route = route;
}
public String getRoute() {
return route;
}
}

View File

@@ -31,6 +31,8 @@ public class NtcDdosLog extends LogEntity<NtcDdosLog> {
protected String attackTotalByte;
@ApiModelProperty(value = "攻击流量是否被丢弃", required = true)
protected Integer isBlcok;
protected String searchIsBlcok; //攻击流量是否被丢弃
public Integer getAttackType() {
return attackType;
}
@@ -80,4 +82,11 @@ public class NtcDdosLog extends LogEntity<NtcDdosLog> {
this.isBlcok = isBlcok;
}
public String getSearchIsBlcok() {
return searchIsBlcok;
}
public void setSearchIsBlcok(String searchIsBlcok) {
this.searchIsBlcok = searchIsBlcok;
}
}

View File

@@ -0,0 +1,31 @@
package com.nis.domain.restful;
import com.nis.domain.LogEntity;
import com.wordnik.swagger.annotations.ApiModelProperty;
/**
* @ClassName:NtcStreamMediaLog
* @Description:TODO(这里用一句话描述这个类的作用)
* @author zdx
* @date 2018年10月9日 下午6:33:11
* @version V1.0
*/
public class NtcStreamMediaLog extends LogEntity<NtcStreamMediaLog> {
private static final long serialVersionUID = 8993339583170276251L;
@ApiModelProperty(value="节目访问地址", required=true)
protected String url;
@ApiModelProperty(value="传输协议", required=true)
protected String protocol;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
}

View File

@@ -0,0 +1,70 @@
package com.nis.domain.restful;
import com.nis.domain.LogEntity;
import com.wordnik.swagger.annotations.ApiModelProperty;
/**
*
* @ClassName:NtcVoipLog
* @Description:TODO(这里用一句话描述这个类的作用)
* @author zdx
* @date 2018年10月9日 下午6:30:10
* @version V1.0
*/
public class NtcVoipLog extends LogEntity<NtcVoipLog> {
private static final long serialVersionUID = -1868408809534973479L;
@ApiModelProperty(value="VOIP通话时长", required=true)
protected String duation;
@ApiModelProperty(value="VOIP协议", required=true)
protected String voipProtocol;
@ApiModelProperty(value="主叫VOIP账号", required=true)
protected String callingAccount;
@ApiModelProperty(value="被叫VOIP账号", required=true)
protected String calledAccount;
@ApiModelProperty(value="VOIP主叫电信号码", required=true)
protected String callingNumber;
@ApiModelProperty(value="VOIP被叫电信号码", required=true)
protected String calledNumber;
/**
* @return the voipProtocol
*/
public String getVoipProtocol() {
return voipProtocol;
}
/**
* @param voipProtocol the voipProtocol to set
*/
public void setVoipProtocol(String voipProtocol) {
this.voipProtocol = voipProtocol;
}
public String getDuation() {
return duation;
}
public void setDuation(String duation) {
this.duation = duation;
}
public String getCallingAccount() {
return callingAccount;
}
public void setCallingAccount(String callingAccount) {
this.callingAccount = callingAccount;
}
public String getCalledAccount() {
return calledAccount;
}
public void setCalledAccount(String calledAccount) {
this.calledAccount = calledAccount;
}
public String getCallingNumber() {
return callingNumber;
}
public void setCallingNumber(String callingNumber) {
this.callingNumber = callingNumber;
}
public String getCalledNumber() {
return calledNumber;
}
public void setCalledNumber(String calledNumber) {
this.calledNumber = calledNumber;
}
}

View File

@@ -8,6 +8,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -19,6 +20,7 @@ import com.nis.domain.Page;
import com.nis.domain.restful.MmAvIpLog;
import com.nis.domain.restful.MmAvUrlLog;
import com.nis.domain.restful.MmFaceRecognizationLog;
import com.nis.domain.restful.MmFileDigestLog;
import com.nis.domain.restful.MmLogoDetectionLog;
import com.nis.domain.restful.MmPicIpLog;
import com.nis.domain.restful.MmPicUrlLog;
@@ -275,7 +277,7 @@ public class MmLogSearchController extends BaseRestController {
return serviceLogResponse(auditLogThread, System.currentTimeMillis() - start, request, "图片URL日志检索成功",
logPage, 0);
}
@Deprecated
@RequestMapping(value = "/mmVoipIpLogs", method = RequestMethod.GET)
@ApiOperation(value = "VoIP IP日志查询", httpMethod = "GET", notes = "对日志功能“VoIP IP日志”提供数据基础查询服务")
public Map<String, ?> mmVoipIpLogs(Page page, MmVoipIpLog mmVoipIpLog, Model model, HttpServletRequest request,
@@ -329,6 +331,7 @@ public class MmLogSearchController extends BaseRestController {
return serviceLogResponse(auditLogThread, System.currentTimeMillis() - start, request, "VoIP IP日志检索成功",
logPage, 0);
}
@Deprecated
@RequestMapping(value = "/mmVoipAccountLogs", method = RequestMethod.GET)
@ApiOperation(value = "VoIP Account日志查询", httpMethod = "GET", notes = "对日志功能“VoIP Account日志”提供数据基础查询服务")
public Map<String, ?> mmVoipAccountLogs(Page page, MmVoipAccountLog mmVoipLog, Model model, HttpServletRequest request,
@@ -865,6 +868,60 @@ public class MmLogSearchController extends BaseRestController {
logPage, 0);
}
@RequestMapping(value = "/mmFileDigestLogs", method = RequestMethod.GET)
@ApiOperation(value = "文件摘要日志查询", httpMethod = "GET", notes = "对日志功能“文件摘要日志”提供数据基础查询服务")
public Map<String, ?> mmFileDigestLogs(Page page, MmFileDigestLog mmFileDigestLog, Model model, HttpServletRequest request,
HttpServletResponse response) {
long start = System.currentTimeMillis();
AuditLogThread auditLogThread = super.saveRequestLog(servicesRequestLogService, Constants.OPACTION_GET,
request, null);
Page<MmFileDigestLog> logPage = null;
try {
resetTime(mmFileDigestLog);
ntcLogService.queryConditionCheck(auditLogThread, start, mmFileDigestLog, MmFileDigestLog.class, page);
logPage = new Page<MmFileDigestLog>();
logPage.setPageNo(page.getPageNo());
logPage.setPageSize(page.getPageSize());
String orderBy = "";
if (null != page.getOrderBy() && !page.getOrderBy().equals("")) {
orderBy = Page.getOrderBySql(MmFileDigestLog.class.getSimpleName(), page.getOrderBy());
} else {
orderBy = "found_Time";
}
String sql = HiveSqlService.getSql(page, mmFileDigestLog,
getTableName(MmFileDigestLog.class.getSimpleName() + "HiveTable", "MM_FILE_DIGEST_LOG"),
getCol2Col(), orderBy, null);
Map<String, List<Object>> tableMapping = new LogJDBCByDruid().tableMapping(page, null, sql, MmFileDigestLog.class, "foundTime",
"recvTime");
if (tableMapping == null) {
logPage.setList(new ArrayList());
} else {
List list = tableMapping.get("obj");
if (list.size() > 0) {
String jsonString = JsonMapper.toJsonString(list);
List<MmFileDigestLog> List = (java.util.List<MmFileDigestLog>) JsonMapper.fromJsonList(jsonString,
MmFileDigestLog.class);
logPage.setList(List);
logPage.setCount(List.size());
} else {
logPage.setList(new ArrayList());
}
}
} catch (Exception e) {
e.printStackTrace();
auditLogThread.setExceptionInfo(e.getMessage() + " " + e.getCause());
logger.error(e);
if (!(e instanceof RestServiceException)) {
e = new RestServiceException(auditLogThread, System.currentTimeMillis() - start, "文件摘要日志检索失败");
}
throw ((RestServiceException) e);
}
return serviceLogResponse(auditLogThread, System.currentTimeMillis() - start, request, "文件摘要日志检索成功",
logPage, 0);
}
/**
*判断开始和结束时间是否为null,如果为null则初始化时间
* @param entity

View File

@@ -31,6 +31,8 @@ import com.nis.domain.restful.NtcP2pLog;
import com.nis.domain.restful.NtcPptpLog;
import com.nis.domain.restful.NtcSshLog;
import com.nis.domain.restful.NtcSslLog;
import com.nis.domain.restful.NtcStreamMediaLog;
import com.nis.domain.restful.NtcVoipLog;
import com.nis.restful.RestServiceException;
import com.nis.util.Configurations;
import com.nis.util.Constants;
@@ -968,7 +970,115 @@ public class NtcLogSearchController extends BaseRestController {
return serviceLogResponse(auditLogThread, System.currentTimeMillis() - start, request, "BGP日志检索成功",
ntcBgpLogPage, 0);
}
@RequestMapping(value = "/ntcVoipLogs", method = RequestMethod.GET)
@ApiOperation(value = "NTC VoIP日志查询", httpMethod = "GET", notes = "对日志功能“NTC VoIP日志”提供数据基础查询服务")
public Map<String, ?> ntcVoipLogs(Page page, NtcVoipLog ntcVoipLog, Model model, HttpServletRequest request,
HttpServletResponse response) {
long start = System.currentTimeMillis();
AuditLogThread auditLogThread = super.saveRequestLog(servicesRequestLogService, Constants.OPACTION_GET,
request, null);
Page<NtcVoipLog> logPage = null;
try {
resetTime(ntcVoipLog);
ntcLogService.queryConditionCheck(auditLogThread, start, ntcVoipLog, NtcVoipLog.class, page);
logPage = new Page<NtcVoipLog>();
logPage.setPageNo(page.getPageNo());
logPage.setPageSize(page.getPageSize());
String orderBy = "";
if (null != page.getOrderBy() && !page.getOrderBy().equals("")) {
orderBy = Page.getOrderBySql(NtcVoipLog.class.getSimpleName(), page.getOrderBy());
} else {
orderBy = "found_Time";
}
String sql = HiveSqlService.getSql(page, ntcVoipLog,
getTableName(NtcVoipLog.class.getSimpleName() + "HiveTable", "NTC_VOIP_LOG"),
getCol2Col(), orderBy, null);
Map<String, List<Object>> tableMapping = new LogJDBCByDruid().tableMapping(page, null, sql, NtcVoipLog.class, "foundTime",
"recvTime");
if (tableMapping == null) {
logPage.setList(new ArrayList());
} else {
List list = tableMapping.get("obj");
if (list.size() > 0) {
String jsonString = JsonMapper.toJsonString(list);
List<NtcVoipLog> List = (java.util.List<NtcVoipLog>) JsonMapper.fromJsonList(jsonString,
NtcVoipLog.class);
logPage.setList(List);
logPage.setCount(List.size());
} else {
logPage.setList(new ArrayList());
}
}
} catch (Exception e) {
e.printStackTrace();
auditLogThread.setExceptionInfo(e.getMessage() + " " + e.getCause());
logger.error(e);
if (!(e instanceof RestServiceException)) {
e = new RestServiceException(auditLogThread, System.currentTimeMillis() - start, "NTC VoIP日志检索失败");
}
throw ((RestServiceException) e);
}
return serviceLogResponse(auditLogThread, System.currentTimeMillis() - start, request, "NTC VoIP日志检索成功",
logPage, 0);
}
@RequestMapping(value = "/ntcStreamMediaLogs", method = RequestMethod.GET)
@ApiOperation(value = "流媒体协议日志查询", httpMethod = "GET", notes = "对日志功能“流媒体协议日志”提供数据基础查询服务")
public Map<String, ?> ntcStreamMediaLogs(Page page, NtcStreamMediaLog ntcStreamMediaLog, Model model, HttpServletRequest request,
HttpServletResponse response) {
long start = System.currentTimeMillis();
AuditLogThread auditLogThread = super.saveRequestLog(servicesRequestLogService, Constants.OPACTION_GET,
request, null);
Page<NtcStreamMediaLog> logPage = null;
try {
resetTime(ntcStreamMediaLog);
ntcLogService.queryConditionCheck(auditLogThread, start, ntcStreamMediaLog, NtcStreamMediaLog.class, page);
logPage = new Page<NtcStreamMediaLog>();
logPage.setPageNo(page.getPageNo());
logPage.setPageSize(page.getPageSize());
String orderBy = "";
if (null != page.getOrderBy() && !page.getOrderBy().equals("")) {
orderBy = Page.getOrderBySql(NtcStreamMediaLog.class.getSimpleName(), page.getOrderBy());
} else {
orderBy = "found_Time";
}
String sql = HiveSqlService.getSql(page, ntcStreamMediaLog,
getTableName(NtcStreamMediaLog.class.getSimpleName() + "HiveTable", "NTC_STREAM_MEDIA_LOG"),
getCol2Col(), orderBy, null);
Map<String, List<Object>> tableMapping = new LogJDBCByDruid().tableMapping(page, null, sql, NtcStreamMediaLog.class, "foundTime",
"recvTime");
if (tableMapping == null) {
logPage.setList(new ArrayList());
} else {
List list = tableMapping.get("obj");
if (list.size() > 0) {
String jsonString = JsonMapper.toJsonString(list);
List<NtcStreamMediaLog> List = (java.util.List<NtcStreamMediaLog>) JsonMapper.fromJsonList(jsonString,
NtcStreamMediaLog.class);
logPage.setList(List);
logPage.setCount(List.size());
} else {
logPage.setList(new ArrayList());
}
}
} catch (Exception e) {
e.printStackTrace();
auditLogThread.setExceptionInfo(e.getMessage() + " " + e.getCause());
logger.error(e);
if (!(e instanceof RestServiceException)) {
e = new RestServiceException(auditLogThread, System.currentTimeMillis() - start, "流媒体协议日志检索失败");
}
throw ((RestServiceException) e);
}
return serviceLogResponse(auditLogThread, System.currentTimeMillis() - start, request, "流媒体协议日志检索成功",
logPage, 0);
}
/**
*判断开始和结束时间是否为null,如果为null则初始化时间
* @param entity

View File

@@ -1024,12 +1024,6 @@
<result column="scene_file" jdbcType="VARCHAR" property="sceneFile" />
</resultMap>
<resultMap id="MmLogoDetectionLogMap" type="com.nis.domain.restful.MmLogoDetectionLog">
<result column="cfg_id" jdbcType="BIGINT" property="cfgId" />
<result column="found_Time" jdbcType="TIMESTAMP" property="foundTime" />
@@ -1132,25 +1126,102 @@
<result column="fd_type" jdbcType="INTEGER" property="fdType" />
<result column="protocol" jdbcType="VARCHAR" property="protocol" />
</resultMap>
<resultMap id="NtcVoipLogMap" type="com.nis.domain.restful.NtcVoipLog">
<result column="cfg_id" jdbcType="BIGINT" property="cfgId" />
<result column="found_Time" jdbcType="TIMESTAMP" property="foundTime" />
<result column="recv_Time" jdbcType="TIMESTAMP" property="recvTime" />
<result column="trans_proto" jdbcType="VARCHAR" property="transProto" />
<result column="addr_type" jdbcType="INTEGER" property="addrType" />
<result column="d_ip" jdbcType="VARCHAR" property="dIp" />
<result column="s_ip" jdbcType="VARCHAR" property="sIp" />
<result column="d_port" jdbcType="VARCHAR" property="dPort" />
<result column="s_port" jdbcType="VARCHAR" property="sPort" />
<result column="service" jdbcType="INTEGER" property="service" />
<result column="entrance_id" jdbcType="BIGINT" property="entranceId" />
<result column="device_id" jdbcType="INTEGER" property="deviceId" />
<result column="direction" jdbcType="INTEGER" property="direction" />
<result column="stream_dir" jdbcType="INTEGER" property="streamDir" />
<result column="cap_ip" jdbcType="VARCHAR" property="capIp" />
<result column="addr_list" jdbcType="VARCHAR" property="addrList" />
<result column="user_region" jdbcType="VARCHAR" property="userRegion" />
<result column="server_locate" jdbcType="VARCHAR" property="serverLocate" />
<result column="client_locate" jdbcType="VARCHAR" property="clientLocate" />
<result column="s_asn" jdbcType="VARCHAR" property="sAsn" />
<result column="d_asn" jdbcType="VARCHAR" property="dAsn" />
<result column="s_subscribe_id" jdbcType="VARCHAR" property="sSubscribeId" />
<result column="d_subscribe_id" jdbcType="VARCHAR" property="dSubscribeId" />
<result column="scene_file" jdbcType="VARCHAR" property="sceneFile" />
<result column="duation" jdbcType="VARCHAR" property="duation" />
<result column="voip_protocol" jdbcType="VARCHAR" property="voipProtocol" />
<result column="calling_account" jdbcType="VARCHAR" property="callingAccount" />
<result column="called_account" jdbcType="VARCHAR" property="calledAccount" />
<result column="calling_number" jdbcType="VARCHAR" property="callingNumber" />
<result column="called_number" jdbcType="VARCHAR" property="calledNumber" />
</resultMap>
<resultMap id="NtcStreamMediaLogMap" type="com.nis.domain.restful.NtcStreamMediaLog">
<result column="cfg_id" jdbcType="BIGINT" property="cfgId" />
<result column="found_Time" jdbcType="TIMESTAMP" property="foundTime" />
<result column="recv_Time" jdbcType="TIMESTAMP" property="recvTime" />
<result column="trans_proto" jdbcType="VARCHAR" property="transProto" />
<result column="addr_type" jdbcType="INTEGER" property="addrType" />
<result column="d_ip" jdbcType="VARCHAR" property="dIp" />
<result column="s_ip" jdbcType="VARCHAR" property="sIp" />
<result column="d_port" jdbcType="VARCHAR" property="dPort" />
<result column="s_port" jdbcType="VARCHAR" property="sPort" />
<result column="service" jdbcType="INTEGER" property="service" />
<result column="entrance_id" jdbcType="BIGINT" property="entranceId" />
<result column="device_id" jdbcType="INTEGER" property="deviceId" />
<result column="direction" jdbcType="INTEGER" property="direction" />
<result column="stream_dir" jdbcType="INTEGER" property="streamDir" />
<result column="cap_ip" jdbcType="VARCHAR" property="capIp" />
<result column="addr_list" jdbcType="VARCHAR" property="addrList" />
<result column="user_region" jdbcType="VARCHAR" property="userRegion" />
<result column="server_locate" jdbcType="VARCHAR" property="serverLocate" />
<result column="client_locate" jdbcType="VARCHAR" property="clientLocate" />
<result column="s_asn" jdbcType="VARCHAR" property="sAsn" />
<result column="d_asn" jdbcType="VARCHAR" property="dAsn" />
<result column="s_subscribe_id" jdbcType="VARCHAR" property="sSubscribeId" />
<result column="d_subscribe_id" jdbcType="VARCHAR" property="dSubscribeId" />
<result column="scene_file" jdbcType="VARCHAR" property="sceneFile" />
<result column="url" jdbcType="VARCHAR" property="url" />
<result column="protocol" jdbcType="VARCHAR" property="protocol" />
</resultMap>
<resultMap id="MmFileDigestLogMap" type="com.nis.domain.restful.MmFileDigestLog">
<result column="cfg_id" jdbcType="BIGINT" property="cfgId" />
<result column="found_Time" jdbcType="TIMESTAMP" property="foundTime" />
<result column="recv_Time" jdbcType="TIMESTAMP" property="recvTime" />
<result column="trans_proto" jdbcType="VARCHAR" property="transProto" />
<result column="addr_type" jdbcType="INTEGER" property="addrType" />
<result column="d_ip" jdbcType="VARCHAR" property="dIp" />
<result column="s_ip" jdbcType="VARCHAR" property="sIp" />
<result column="d_port" jdbcType="VARCHAR" property="dPort" />
<result column="s_port" jdbcType="VARCHAR" property="sPort" />
<result column="service" jdbcType="INTEGER" property="service" />
<result column="entrance_id" jdbcType="BIGINT" property="entranceId" />
<result column="device_id" jdbcType="INTEGER" property="deviceId" />
<result column="direction" jdbcType="INTEGER" property="direction" />
<result column="stream_dir" jdbcType="INTEGER" property="streamDir" />
<result column="cap_ip" jdbcType="VARCHAR" property="capIp" />
<result column="addr_list" jdbcType="VARCHAR" property="addrList" />
<result column="user_region" jdbcType="VARCHAR" property="userRegion" />
<result column="server_locate" jdbcType="VARCHAR" property="serverLocate" />
<result column="client_locate" jdbcType="VARCHAR" property="clientLocate" />
<result column="s_asn" jdbcType="VARCHAR" property="sAsn" />
<result column="d_asn" jdbcType="VARCHAR" property="dAsn" />
<result column="s_subscribe_id" jdbcType="VARCHAR" property="sSubscribeId" />
<result column="d_subscribe_id" jdbcType="VARCHAR" property="dSubscribeId" />
<result column="scene_file" jdbcType="VARCHAR" property="sceneFile" />
<result column="pid" jdbcType="VARCHAR" property="pid" />
<result column="url" jdbcType="VARCHAR" property="url" />
<result column="log_uri" jdbcType="VARCHAR" property="logUri" />
<result column="referer" jdbcType="VARCHAR" property="referer" />
<result column="level" jdbcType="INTEGER" property="level" />
<result column="fd_type" jdbcType="INTEGER" property="fdType" />
<result column="protocol" jdbcType="VARCHAR" property="protocol" />
</resultMap>
</mapper>