1:为日志查询添加请求内容的条件

2:将请求日志表分为配置请求记录表和日志请求记录表
3:更改redis数据同步到集群的策略
This commit is contained in:
renkaige
2018-10-19 17:17:04 +08:00
parent ddf64439ea
commit f6c056231c
8 changed files with 313 additions and 68 deletions

View File

@@ -17,7 +17,7 @@ public class SwaggerLog extends BaseEntity<SwaggerLog> {
private String serverIp; private String serverIp;
private String traceCode; private String traceCode;
private String requestUri; private String requestUri;
private String tableName;
private String exception;// 界面的异常信息框 private String exception;// 界面的异常信息框
private Date beginDate; // 开始日期 private Date beginDate; // 开始日期
@@ -151,4 +151,12 @@ public class SwaggerLog extends BaseEntity<SwaggerLog> {
this.requestUri = requestUri; this.requestUri = requestUri;
} }
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
} }

View File

@@ -3,6 +3,7 @@ package com.nis.web.controller.sys;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@@ -27,8 +28,16 @@ public class SysLogController extends BaseController {
@RequiresPermissions("sys:swaggerLogList:view") @RequiresPermissions("sys:swaggerLogList:view")
@RequestMapping(value = "swaggerLogList") @RequestMapping(value = "swaggerLogList")
public String swaggerLogList(SwaggerLog swaggerLog, HttpServletRequest request, HttpServletResponse response, Model model) { public String swaggerLogList(SwaggerLog swaggerLog, HttpServletRequest request, HttpServletResponse response,
Page<SwaggerLog> page = swaggerLogService.findSwaggerLogPage(new Page<SwaggerLog>(request, response), swaggerLog); Model model) {
if (swaggerLog.getOpAction() != null && swaggerLog.getOpAction() == 4) {
swaggerLog.setTableName("services_log_request_log");
} else {
swaggerLog.setTableName("services_config_request_log");
}
swaggerLog.setRequestContent(StringEscapeUtils.unescapeHtml4(swaggerLog.getRequestContent()));
Page<SwaggerLog> page = swaggerLogService.findSwaggerLogPage(new Page<SwaggerLog>(request, response),
swaggerLog);
model.addAttribute("page", page); model.addAttribute("page", page);
return "/sys/swaggerLogList"; return "/sys/swaggerLogList";
} }

View File

@@ -16,6 +16,8 @@ import com.nis.domain.restful.ServicesRequestLogBean;
public interface ServicesRequestLogDao { public interface ServicesRequestLogDao {
int insert(ServicesRequestLog record); int insert(ServicesRequestLog record);
int insertConfigLog(ServicesRequestLog record);
int insertLogLog(ServicesRequestLog record);
public void deleteById(Long id); public void deleteById(Long id);

View File

@@ -176,4 +176,185 @@
</if> </if>
</trim> </trim>
</insert> </insert>
<insert id="insertConfigLog" parameterType="com.nis.domain.ServicesRequestLog">
insert into SERVICES_CONFIG_REQUEST_LOG
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="operator != null">
OPERATOR,
</if>
<if test="version != null">
VERSION,
</if>
<if test="opAction != null">
OPACTION,
</if>
<if test="opTime != null">
OPTIME,
</if>
<if test="requestContent != null">
REQUEST_CONTENT,
</if>
<if test="requestTime != null">
REQUEST_TIME,
</if>
<if test="consumerTime != null">
CONSUMER_TIME,
</if>
<if test="requestIp != null">
REQUEST_IP,
</if>
<if test="businessCode != null">
BUSINESS_CODE,
</if>
<if test="exceptionInfo != null">
EXCEPTION_INFO,
</if>
<if test="serverIp != null">
SERVER_IP,
</if>
<if test="traceCode != null">
TRACE_CODE,
</if>
<if test="requestURI != null">
REQUEST_URI,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="operator != null">
#{operator,jdbcType=VARCHAR},
</if>
<if test="version != null">
#{version,jdbcType=VARCHAR},
</if>
<if test="opAction != null">
#{opAction,jdbcType=INTEGER},
</if>
<if test="opTime != null">
#{opTime,jdbcType=TIMESTAMP},
</if>
<if test="requestContent != null">
#{requestContent,jdbcType=CLOB},
</if>
<if test="requestTime != null">
#{requestTime,jdbcType=TIMESTAMP},
</if>
<if test="consumerTime != null">
#{consumerTime,jdbcType=BIGINT},
</if>
<if test="requestIp != null">
#{requestIp,jdbcType=VARCHAR},
</if>
<if test="businessCode != null">
#{businessCode,jdbcType=INTEGER},
</if>
<if test="exceptionInfo != null">
#{exceptionInfo,jdbcType=VARCHAR},
</if>
<if test="serverIp != null">
#{serverIp,jdbcType=VARCHAR},
</if>
<if test="traceCode != null">
#{traceCode,jdbcType=VARCHAR},
</if>
<if test="requestURI != null">
#{requestURI,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<insert id="insertLogLog" parameterType="com.nis.domain.ServicesRequestLog">
insert into SERVICES_LOG_REQUEST_LOG
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="operator != null">
OPERATOR,
</if>
<if test="version != null">
VERSION,
</if>
<if test="opAction != null">
OPACTION,
</if>
<if test="opTime != null">
OPTIME,
</if>
<if test="requestContent != null">
REQUEST_CONTENT,
</if>
<if test="requestTime != null">
REQUEST_TIME,
</if>
<if test="consumerTime != null">
CONSUMER_TIME,
</if>
<if test="requestIp != null">
REQUEST_IP,
</if>
<if test="businessCode != null">
BUSINESS_CODE,
</if>
<if test="exceptionInfo != null">
EXCEPTION_INFO,
</if>
<if test="serverIp != null">
SERVER_IP,
</if>
<if test="traceCode != null">
TRACE_CODE,
</if>
<if test="requestURI != null">
REQUEST_URI,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="operator != null">
#{operator,jdbcType=VARCHAR},
</if>
<if test="version != null">
#{version,jdbcType=VARCHAR},
</if>
<if test="opAction != null">
#{opAction,jdbcType=INTEGER},
</if>
<if test="opTime != null">
#{opTime,jdbcType=TIMESTAMP},
</if>
<if test="requestContent != null">
#{requestContent,jdbcType=CLOB},
</if>
<if test="requestTime != null">
#{requestTime,jdbcType=TIMESTAMP},
</if>
<if test="consumerTime != null">
#{consumerTime,jdbcType=BIGINT},
</if>
<if test="requestIp != null">
#{requestIp,jdbcType=VARCHAR},
</if>
<if test="businessCode != null">
#{businessCode,jdbcType=INTEGER},
</if>
<if test="exceptionInfo != null">
#{exceptionInfo,jdbcType=VARCHAR},
</if>
<if test="serverIp != null">
#{serverIp,jdbcType=VARCHAR},
</if>
<if test="traceCode != null">
#{traceCode,jdbcType=VARCHAR},
</if>
<if test="requestURI != null">
#{requestURI,jdbcType=VARCHAR},
</if>
</trim>
</insert>
</mapper> </mapper>

View File

@@ -5,7 +5,7 @@
<select id="findList" resultType="swaggerLog"> <select id="findList" resultType="swaggerLog">
SELECT SELECT
a.* a.*
FROM services_request_log a FROM ${tableName} a
WHERE a.request_time BETWEEN WHERE a.request_time BETWEEN
#{beginDate} AND #{endDate} #{beginDate} AND #{endDate}

View File

@@ -51,8 +51,13 @@ public class ServicesRequestLogService {
@Autowired @Autowired
public ServicesRequestLogDao servicesRequestLogDao; public ServicesRequestLogDao servicesRequestLogDao;
private void saveLog(ServicesRequestLog log) { private void saveLog(ServicesRequestLog log, boolean isPzLog) {
servicesRequestLogDao.insert(log); if (isPzLog) {
servicesRequestLogDao.insertConfigLog(log);
} else {
servicesRequestLogDao.insertLogLog(log);
}
} }
/** /**
@@ -66,8 +71,7 @@ public class ServicesRequestLogService {
* @param queryString * @param queryString
* request中的参数 * request中的参数
* @param contextPath * @param contextPath
* request中的参数 * request中的参数 响应
* 响应
* @param operator * @param operator
* 操作人 * 操作人
* @param version * @param version
@@ -82,13 +86,13 @@ public class ServicesRequestLogService {
* 请求到达服务器时间 * 请求到达服务器时间
* @param consumerTime * @param consumerTime
* 耗时 void * 耗时 void
* @throws UnknownHostException * @throws UnknownHostException
* @exception @since * @exception @since
* 1.0.0 * 1.0.0
*/ */
public void saveRequestLog(String requestAddr, String requestURI, String queryString, String contextPath, public void saveRequestLog(String requestAddr, String requestURI, String queryString, String contextPath,
String operator, String version, int opAction, Date opTime, Object content, Date requestTime, String operator, String version, int opAction, Date opTime, Object content, Date requestTime,
long consumerTime, int businessCode, String exceptionInfo,String traceCode) { long consumerTime, int businessCode, String exceptionInfo, String traceCode) {
logger.info("开始记录日志---"); logger.info("开始记录日志---");
logger.info("请求IP---" + requestAddr); logger.info("请求IP---" + requestAddr);
logger.info("请求路径---" + requestURI); logger.info("请求路径---" + requestURI);
@@ -107,36 +111,37 @@ public class ServicesRequestLogService {
log.setTraceCode(traceCode); log.setTraceCode(traceCode);
log.setRequestURI(requestURI); log.setRequestURI(requestURI);
try { try {
if(Constants.SERVCER_HOST!=null){ if (Constants.SERVCER_HOST != null) {
log.setServerIp(Constants.SERVCER_HOST); log.setServerIp(Constants.SERVCER_HOST);
}else if(isWindows()){ } else if (isWindows()) {
Constants.SERVCER_HOST=InetAddress.getLocalHost().getHostAddress(); Constants.SERVCER_HOST = InetAddress.getLocalHost().getHostAddress();
log.setServerIp(Constants.SERVCER_HOST); log.setServerIp(Constants.SERVCER_HOST);
}else{ } else {
InetAddress ip=null; InetAddress ip = null;
boolean bFindIP=false; boolean bFindIP = false;
Enumeration<NetworkInterface> netInterfaces=(Enumeration<NetworkInterface>) Enumeration<NetworkInterface> netInterfaces = (Enumeration<NetworkInterface>) NetworkInterface
NetworkInterface.getNetworkInterfaces(); .getNetworkInterfaces();
while(netInterfaces.hasMoreElements()){ while (netInterfaces.hasMoreElements()) {
if(bFindIP){ if (bFindIP) {
break; break;
} }
NetworkInterface ni=(NetworkInterface)netInterfaces.nextElement(); NetworkInterface ni = (NetworkInterface) netInterfaces.nextElement();
Enumeration<InetAddress> ips=ni.getInetAddresses(); Enumeration<InetAddress> ips = ni.getInetAddresses();
while(ips.hasMoreElements()){ while (ips.hasMoreElements()) {
ip=(InetAddress)ips.nextElement(); ip = (InetAddress) ips.nextElement();
if(ip.isSiteLocalAddress()&&!ip.isLoopbackAddress()&&ip.getHostAddress().indexOf(":")==-1){ if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress()
bFindIP=true; && ip.getHostAddress().indexOf(":") == -1) {
bFindIP = true;
break; break;
} }
} }
} }
if(null !=ip){ if (null != ip) {
Constants.SERVCER_HOST=ip.getHostAddress(); Constants.SERVCER_HOST = ip.getHostAddress();
log.setServerIp(ip.getHostAddress()); log.setServerIp(ip.getHostAddress());
} }
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
logger.error("无法获取当前服务器ip"); logger.error("无法获取当前服务器ip");
e.printStackTrace(); e.printStackTrace();
@@ -173,19 +178,25 @@ public class ServicesRequestLogService {
} }
} }
this.saveLog(log); if (log.getRequestURI() == null || log.getRequestURI().toLowerCase().contains("service/log")) {
this.saveLog(log, false);
} else {
this.saveLog(log, true);
}
logger.info("记录日志完成---"); logger.info("记录日志完成---");
} }
public boolean isWindows(){
boolean isWindows=false; public boolean isWindows() {
if(System.getProperty("os.name").toLowerCase().indexOf("windows")>-1){ boolean isWindows = false;
isWindows=true; if (System.getProperty("os.name").toLowerCase().indexOf("windows") > -1) {
isWindows = true;
} }
return isWindows; return isWindows;
} }
/** /**
* 直接从数据源获取connection保存日志的方法用saveLog()方法就行 save(这里用一句话描述这个方法的作用) * 直接从数据源获取connection保存日志的方法用saveLog()方法就行 save(这里用一句话描述这个方法的作用) (这里描述这个方法适用条件
* (这里描述这个方法适用条件 可选) * 可选)
* *
* @param data * @param data
* @throws IllegalArgumentException * @throws IllegalArgumentException
@@ -302,7 +313,7 @@ public class ServicesRequestLogService {
public void deleteById(String id) { public void deleteById(String id) {
String[] split = id.split(","); String[] split = id.split(",");
for (String str : split) { for (String str : split) {
if(!str.equals("")){ if (!str.equals("")) {
servicesRequestLogDao.deleteById(Long.parseLong(str)); servicesRequestLogDao.deleteById(Long.parseLong(str));
} }
} }

View File

@@ -37,39 +37,47 @@ public class SyncRedisToCluster {
// private JedisSentinelPool jedisSentinelPool; // private JedisSentinelPool jedisSentinelPool;
// @Scheduled(cron = "0/3 * * * * ?") // @Scheduled(cron = "0/3 * * * * ?")
// @Scheduled(cron = "${syncRedisToClusterCron}") // @Scheduled(cron = "${syncRedisToClusterCron}")
public void syncRedisToCluster() { public void syncRedisToCluster() {
try { try {
// keys("EFFECTIVE_RULE*"); String rpopRedisList = rpopRedisList("SyncRedisToCluster");
// keys("OBSOLETE_RULE*"); if (rpopRedisList != null) {//避免集群环境下同一秒钟所有的机器都执行这个定时任务
String clusterMaatVersionStr = jedisCluster.get("MAAT_VERSION"); // keys("EFFECTIVE_RULE*");
String redisMaatVersionStr = JedisUtils.get("MAAT_VERSION", redisStatisticsRealDBIndex); // keys("OBSOLETE_RULE*");
if (clusterMaatVersionStr != null && !clusterMaatVersionStr.trim().equals("")) { String clusterMaatVersionStr = jedisCluster.get("MAAT_VERSION");
if (redisMaatVersionStr != null && !redisMaatVersionStr.trim().equals("")) { String redisMaatVersionStr = JedisUtils.get("MAAT_VERSION", redisStatisticsRealDBIndex);
Integer clusterMaatVersion = Integer.valueOf(clusterMaatVersionStr); if (clusterMaatVersionStr != null && !clusterMaatVersionStr.trim().equals("")) {
Integer redisMaatVersion = Integer.valueOf(redisMaatVersionStr); if (redisMaatVersionStr != null && !redisMaatVersionStr.trim().equals("")) {
if (redisMaatVersion < clusterMaatVersion) {// 如果主从库比集群库的版本号小则下发全量 Integer clusterMaatVersion = Integer.valueOf(clusterMaatVersionStr);
logger.info("redis集群中的MAAT_VERSION为大于配置库中的MAAT_VERSION,开始执行全量同步"); Integer redisMaatVersion = Integer.valueOf(redisMaatVersionStr);
syncAllData(redisMaatVersionStr); if (redisMaatVersion < clusterMaatVersion) {// 如果主从库比集群库的版本号小则下发全量
} else if (redisMaatVersion > clusterMaatVersion) {// 获取增量的数据 logger.info("redis集群中的MAAT_VERSION为大于配置库中的MAAT_VERSION,开始执行全量同步");
logger.info("redis集群中的MAAT_VERSION为小于配置库中的MAAT_VERSION,开始执行增量同步,score是{}-{}", syncAllData(redisMaatVersionStr);
clusterMaatVersion, +redisMaatVersion); } else if (redisMaatVersion > clusterMaatVersion) {// 获取增量的数据
syncData(clusterMaatVersion.doubleValue(), redisMaatVersion.doubleValue(), redisMaatVersionStr); logger.info("redis集群中的MAAT_VERSION为小于配置库中的MAAT_VERSION,开始执行增量同步,score是{}-{}",
clusterMaatVersion, +redisMaatVersion);
syncData(clusterMaatVersion.doubleValue(), redisMaatVersion.doubleValue(),
redisMaatVersionStr);
} else {
logger.info("redis集群中的MAAT_VERSION与配置库中的MAAT_VERSION相等,暂不执行配置同步操作");
}
} else { } else {
logger.info("redis集群中的MAAT_VERSION与配置库中的MAAT_VERSION相等,暂不执行配置同步操作"); logger.info("redis配置库中MAAT_VERSION为null,但是redis集群中的MAAT_VERSION为{},集群与配置库的数据不同步,开始删除集群中的配置",
clusterMaatVersionStr);
delClusterData();
} }
} else { } else {
logger.info("redis配置库中MAAT_VERSION为null,但是redis集群中的MAAT_VERSION为{},集群与配置库的数据不同步,开始删除集群中的配置", if (redisMaatVersionStr != null && !redisMaatVersionStr.trim().equals("")) {
clusterMaatVersionStr); logger.info("redis配置库中的MAAT_VERSION为{},redis集群中的MAAT_VERSION为null,开始执行全量同步",
delClusterData(); redisMaatVersionStr);
syncAllData(redisMaatVersionStr);
} else {
logger.info("redis配置库中和redis集群中的MAAT_VERSION都为null,暂时不执行全量同步");
}
} }
lpushRedisList("SyncRedisToCluster", "1");
} else { } else {
if (redisMaatVersionStr != null && !redisMaatVersionStr.trim().equals("")) { logger.info("没有从rediscluster中获取到SyncRedisToCluster的值,暂时不执行数据同步!");
logger.info("redis配置库中的MAAT_VERSION为{},redis集群中的MAAT_VERSION为null,开始执行全量同步", redisMaatVersionStr);
syncAllData(redisMaatVersionStr);
} else {
logger.info("redis配置库中和redis集群中的MAAT_VERSION都为null,暂时不执行全量同步");
}
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("同步配置库配置到3A-redisCluster失败,失败原因:{}", ExceptionUtil.getExceptionMsg(e)); logger.error("同步配置库配置到3A-redisCluster失败,失败原因:{}", ExceptionUtil.getExceptionMsg(e));
@@ -190,4 +198,29 @@ public class SyncRedisToCluster {
logger.info("向redis集群同步数据成功"); logger.info("向redis集群同步数据成功");
} }
/**
* 存储Redis队列顺序存储
*
* @param key
* redis键名
* @param value
* 键值
*/
public void lpushRedisList(String key, String value) {
// LPUSH SyncRedisToCluster "1"
jedisCluster.lpush(key, value);
logger.info("向redis集群的{}中lpush了一个值{}", key, value);
}
/**
* 移除并获取列表最后一个元素
*
* @param key
* @return
*/
public String rpopRedisList(String key) {
// RPOP SyncRedisToCluster
return jedisCluster.rpop(key);
}
} }

View File

@@ -42,19 +42,20 @@
</form:select> </form:select>
<label>请求内容:</label><input id="requestContent" name="requestContent" type="text" class="input-mini" value="<c:out value="${swaggerLog.requestContent}"></c:out>"/>
<label>TRACECODE</label><input id="traceCode" name="traceCode" type="text" maxlength="30" class="input-mini" value="${swaggerLog.traceCode}"/> <label>TRACECODE</label><input id="traceCode" name="traceCode" type="text" maxlength="30" class="input-mini" value="${swaggerLog.traceCode}"/>
<label>BUSINESSCODE</label><input id="businessCode" name="businessCode" type="text" maxlength="30" class="input-mini" value="${swaggerLog.businessCode}"/> <label>BUSINESSCODE</label><input id="businessCode" name="businessCode" type="text" maxlength="30" class="input-mini" value="${swaggerLog.businessCode}"/>
<label>异常信息:</label><input id="exceptionInfo" name="exceptionInfo" type="text" maxlength="500" class="input-mini" value="${swaggerLog.exceptionInfo}"/> <label>异常信息:</label><input id="exceptionInfo" name="exceptionInfo" type="text" maxlength="500" class="input-mini" value="${swaggerLog.exceptionInfo}"/>
<label>请求ip</label><input id="requestIp" name="requestIp" type="text" maxlength="50" class="input-mini" value="${swaggerLog.requestIp}"/> <label>请求ip</label><input id="requestIp" name="requestIp" type="text" maxlength="50" class="input-mini" value="${swaggerLog.requestIp}"/>
<label>服务端ip</label><input id="serverIp" name="serverIp" type="text" maxlength="50" class="input-mini" value="${swaggerLog.serverIp}"/> <label>服务端ip</label><input id="serverIp" name="serverIp" type="text" maxlength="50" class="input-mini" value="${swaggerLog.serverIp}"/>
</div><div style="margin-top:8px;"> </div><div style="margin-top:8px;">
<label>请求日期范围:&nbsp;</label><input id="beginDate" name="beginDate" type="text" readonly="readonly" maxlength="20" class="input-mini Wdate" <label>请求日期范围:&nbsp;</label><input id="beginDate" name="beginDate" type="text" readonly="readonly" maxlength="20" class="input-medium Wdate"
value="<fmt:formatDate value="${swaggerLog.beginDate}" pattern="yyyy-MM-dd"/>" onclick="WdatePicker({dateFmt:'yyyy-MM-dd',isShowClear:false});"/> value="<fmt:formatDate value="${swaggerLog.beginDate}" pattern="yyyy-MM-dd HH:mm:ss"/>" onclick="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss',isShowClear:false});"/>
<label>&nbsp;--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label><input id="endDate" name="endDate" type="text" readonly="readonly" maxlength="20" class="input-mini Wdate" <label>&nbsp;--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label><input id="endDate" name="endDate" type="text" readonly="readonly" maxlength="20" class="input-medium Wdate"
value="<fmt:formatDate value="${swaggerLog.endDate}" pattern="yyyy-MM-dd"/>" onclick="WdatePicker({dateFmt:'yyyy-MM-dd',isShowClear:false});"/>&nbsp;&nbsp; value="<fmt:formatDate value="${swaggerLog.endDate}" pattern="yyyy-MM-dd HH:mm:ss"/>" onclick="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss',isShowClear:false});"/>&nbsp;&nbsp;
&nbsp;<label for="exception"> &nbsp;<label for="exception">
<input id="exception" name="exception" type="checkbox"${swaggerLog.exception eq '1'?' checked':''} value="1"/>只查询异常信息</label> <input id="exception" name="exception" type="checkbox" ${swaggerLog.exception eq '1'?' checked':''} value="1"/>只查询异常信息</label>
&nbsp;&nbsp;&nbsp;<input id="btnSubmit" class="btn btn-primary" type="submit" value="查询"/>&nbsp;&nbsp;<label style="color: red">默认<strong style="font-size: 20px;">不显示查询类</strong>日志,如需显示请选择操作行为</label> &nbsp;&nbsp;&nbsp;<input id="btnSubmit" class="btn btn-primary" type="submit" value="查询"/>&nbsp;&nbsp;<label style="color: red">默认<strong style="font-size: 20px;">不显示查询类</strong>日志,如需显示请选择操作行为</label>
</div> </div>