大資料之電話日誌分析callLog案例(三)
阿新 • • 發佈:2018-11-09
一、查詢使用者最近的通話資訊 -------------------------------------------- 1.實現分析 使用ssm可視介面提供查詢串 -- controller連線 hiveserver2 -- 將命令轉化成hsql語句 -- hive繫結hbase,進行MR聚合查詢 2.啟動yarn叢集 以及其高可用 a.$s100> start-yarn.sh b.$s500> yarn-daemon.sh start resourcemanager c.驗證web介面:http://s100:8088 3.初始化hive a.在mysql中建立存放hive資訊的資料庫 mysql > create database hive; b.初始化hive的元資料(表結構)到mysql中 $> cd /soft/hive/bin $> schematool -dbType mysql -initSchema c.進入hive shell $> hive d.建立mydb資料庫 $hive> create database mydb; e.建立外部表,對映到hbase資料庫 $hive> create external table ext_calllogs_in_hbase (id string, caller string,callTime string,callee string,callDuration string) STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler' WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,f1:caller,f1:callTime,f1:callee,f1:callDuration") TBLPROPERTIES ("hbase.table.name" = "call:calllogs"); f.hive查詢17731086666的最近的10條通話記錄,並按照時間降序排列 $hive> select * from ext_calllogs_in_hbase where id like '%17731086666%' order by callTime desc limit 10; 4.程式設計實現hive查詢 a.新增依賴 <dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-jdbc</artifactId> <version>1.2.1</version> </dependency> b.ssm中建立service,查詢hive表中資料 -- 在ssm模組下建立新包com.ssm.callloghive.HiveService --------------------------------------------------
package com.ssm.callloghive.service; import com.it18zhang.ssm.domain.Calllog; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; //記得添加註解Service @Service public class HiveService { //hiveserver2連線串 private static String url = "jdbc:hive2://s100:10000/" ; //驅動程式類 private static String driverClass = "org.apache.hive.jdbc.HiveDriver" ; static{ try { Class.forName(driverClass); } catch (Exception e) { e.printStackTrace(); } } /** * 查詢最近的通話記錄,使用hive進行mr查詢. */ public Calllog findLatestCallLog(String phoneNum){ try { Connection conn = DriverManager.getConnection(url); Statement st = conn.createStatement(); String sql = "select * from ext_calllogs_in_hbase where id like '%"+ phoneNum+"%' order by callTime desc limit 1" ; ResultSet rs = st.executeQuery(sql); Calllog log = null ; while(rs.next()){ log = new Calllog(); log.setCaller(rs.getString("caller")); log.setCallee(rs.getString("callee")); log.setCallTime(rs.getString("callTime")); log.setCallDuration(rs.getString("callDuration")); } rs.close(); return log; } catch (Exception e) { e.printStackTrace(); } return null ; } }
c.啟動hiveserver2伺服器 $> cd /soft/hive/bin/ $> ./hiveserver2 & d.驗證hiveserver2埠 $> netstat -anop | grep '10000' f.test單元測試 ---------------------- @Test public void testHive() { HiveService hive = new HiveService(); hive.findLatestCallLog("17731086666"); } 5.編寫前端jsp頁面[findLatestCalllog.jsp],實現最近記錄查詢
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>查詢最近的通話記錄</title>
<link rel="stylesheet" type="text/css" href="../css/my.css">
</head>
<body>
<form action="<c:out value="/calllog/findLatestCalllog"/>" method="post">
<table>
<tr>
<td>電話號碼 :</td>
<td><input type="text" name="caller"></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="查詢"/>
</td>
</tr>
</table>
</form>
</body>
</html>
6.編寫控制器 -----------------------------------------
@RequestMapping(value = "calllog/toFindLatestCalllog")
public String toFindLatestCalllogPage()
{
return "calllog/findLatestCalllog";
}
/**
* 接受引數
* @param m
* @return
*/
@RequestMapping(value = "calllog/findLatestCalllog", method = RequestMethod.POST)
public String findLatestCalllog(Model m, @RequestParam("caller") String caller)
{
Calllog log = hcs.findLatestCallLog(caller);
List<Calllog> logs = new ArrayList<Calllog>();
logs.add(log);
m.addAttribute("calllogs", logs);
return "calllog/calllogList" ;
}
7.因為添加了新的hive依賴,所以,ssm專案構建時需要新增新的包到Artifacts File --> project structures --> ssm war exploded --> ...put to libs 8.因為新建了包,所以要在beans.xml中新增新的掃描包路徑 <context:component-scan base-package="com.it18zhang.ssm.dao,com.it18zhang.ssm.service,com.ssm.callloghive.service" /> 9.執行ssm-app,進入網址 http://localhost:8080/calllog/toFindLatestCalllog 10.注意事項: SSM整合hive-jdbc訪問hive的hiveserver2時,需要如下處理: a.使用hive-jdbc-1.2.1的依賴版本 <dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-jdbc</artifactId> <version>1.2.1</version> </dependency> b.需要整合apache-tomcat-9.0.0.M19版本,否則報編譯器錯誤。 二、給通話記錄新增名字資訊 ---------------------------------------------------- 1.修改Calllog類,新增欄位callerName和calleeName -------------------------------------------------
package com.it18zhang.ssm.domain;
import com.it18zhang.ssm.util.CalllogUtil;
import java.text.SimpleDateFormat;
/**
* calllog的domain類 -- 標準javabean
*/
public class Calllog {
private String caller;
//主叫名字
private String callerName;
private String callee;
//被叫名字
private String calleeName;
private String callTime;
private String callDuration;
//是否是主叫
private boolean flag;
public String getCallerName() {
return callerName;
}
public void setCallerName(String callerName) {
this.callerName = callerName;
}
public String getCalleeName() {
return calleeName;
}
public void setCalleeName(String calleeName) {
this.calleeName = calleeName;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getCaller() {
return caller;
}
public void setCaller(String caller) {
this.caller = caller;
}
public String getCallee() {
return callee;
}
public void setCallee(String callee) {
this.callee = callee;
}
public String getCallTime() {
if (callTime != null) {
return CalllogUtil.formatDate(callTime);
}
return null;
}
public void setCallTime(String callTime) {
this.callTime = callTime;
}
public String getCallDuration() {
return callDuration;
}
public void setCallDuration(String callDuration) {
this.callDuration = callDuration;
}
}
2.在mysql中新增人員資訊表persons,然後通過hbase的電話號碼關聯查詢人員名字 $mysql> use mybatis; $mysql> create table persons(id int primary key auto_increment, name varchar(100), phone varchar(20)); 3.在domain中,新增實體類Person --------------------------------------------
package com.it18zhang.ssm.domain;
public class Person {
private Integer id;
private String name;
private String phone;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
4.在dao.impl中新增 ----------------------------------------
package com.it18zhang.ssm.dao.impl;
import com.it18zhang.ssm.dao.BaseDao;
import com.it18zhang.ssm.domain.Person;
import com.it18zhang.ssm.domain.User;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
*/
@Repository("personDao")
public class PersonDaoImpl extends SqlSessionDaoSupport implements BaseDao<Person> {
public void insert(Person person) {
getSqlSession().insert("persons.insert", person);
}
public void update(Person person) {
}
public void delete(Integer id) {
}
public Person selectOne(Integer id) {
return null;
}
public List<Person> selectAll() {
return getSqlSession().selectList("persons.selectAll");
}
public List<Person> selectPage(int offset, int len) {
return null;
}
public int selectCount() {
return 0;
}
//新增通過電話號碼查詢名字
public String selectNameByPhone(String phone) {
return getSqlSession().selectOne("persons.selectNameByPhone", phone);
}
}
5.在resources中新增對映檔案PersonMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="persons">
<select id="selectAll" resultType="_Person">
select * from persons
</select>
<insert id="insert">
insert into persons(name,phone) values(#{name},#{phone})
</insert>
<!-- selectOne -->
<select id="selectNameByPhone" parameterType="string" resultMap="string">
select
name
from persons
where phone = #{phone}
</select>
</mapper>
6.編輯mybatis-config.xml配置檔案,給Person類新增別名並且新增配置對映檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="com.it18zhang.ssm.domain.User" alias="_User"/>
<typeAlias type="com.it18zhang.ssm.domain.Order" alias="_Order"/>
<typeAlias type="com.it18zhang.ssm.domain.Item" alias="_Item"/>
<typeAlias type="com.it18zhang.ssm.domain.Person" alias="_Person"/>
</typeAliases>
<!-- 引入對映檔案 -->
<mappers>
<mapper resource="UserMapper.xml"/>
<mapper resource="OrderMapper.xml"/>
<mapper resource="ItemMapper.xml"/>
<mapper resource="PersonMapper.xml"/>
</mappers>
</configuration>
7.新增PersonService -------------------------------
package com.it18zhang.ssm.service;
import com.it18zhang.ssm.domain.Person;
import com.it18zhang.ssm.domain.User;
import java.util.List;
/**
*
*/
public interface PersonService extends BaseService<Person> {
public String selectNameByPhone(String phone);
}
8.新增PersonServiceImpl ---------------------------------------
package com.it18zhang.ssm.service.impl;
import com.it18zhang.ssm.dao.BaseDao;
import com.it18zhang.ssm.domain.Item;
import com.it18zhang.ssm.domain.Order;
import com.it18zhang.ssm.domain.Person;
import com.it18zhang.ssm.domain.User;
import com.it18zhang.ssm.service.PersonService;
import com.it18zhang.ssm.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
*
*/
@Service("personService")
public class PersonServiceImpl extends BaseServiceImpl<Person> implements PersonService {
@Resource(name = "personDao")
public void setDao(BaseDao<Person> dao) {
super.setDao(dao);
}
public String selectNameByPhone(String phone) {
return ((PersonDaoImpl) getDao()).selectNameByPhone(phone);
}
}
9.新增測試類,檢視是否能夠插入和查詢人員名單 ----------------------------------------------
package com.it18zhang.ssm.test;
import com.it18zhang.ssm.domain.Person;
import com.it18zhang.ssm.service.PersonService;
import com.it18zhang.ssm.service.impl.PersonServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.*;
public class TestPerson {
//電話簿
public static Map<String, String> callers = new HashMap<String, String>();
static{
callers.put("15811111111", "史讓");
callers.put("18022222222", "趙嗄");
callers.put("15133333333", "張錒 ");
callers.put("13269364444", "王以");
callers.put("15032295555", "張噢");
callers.put("17731086666", "張類");
callers.put("15338597777", "李平");
callers.put("15733218888", "杜跑");
callers.put("15614209999", "任陽");
callers.put("15778421111", "樑鵬");
callers.put("18641241111", "郭彤");
callers.put("15732641111", "劉飛");
callers.put("13341101111", "段星");
callers.put("13560191111", "唐華");
callers.put("18301581111", "楊謀");
callers.put("13520401111", "溫英");
callers.put("18332561111", "朱寬");
callers.put("18620191111", "劉宗");
}
@Test
public void testInsertPerson()
{
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
PersonService ps = (PersonService)ac.getBean("personService");
Set<String> keySet = callers.keySet();
for(String key : keySet)
{
String num = key;
String name = callers.get(key);
Person p = new Person();
p.setName(name);
p.setPhone(num);
ps.insert(p);
}
}
@Test
public void testFindAllPerson()
{
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
PersonService ps = (PersonService)ac.getBean("personService");
List<Person> list = ps.selectAll();
for(Person p : list)
{
System.out.println(p.getName() + ":" + p.getPhone());
}
}
@Test
public void testFindPersonNameByPhone()
{
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
PersonService ps = (PersonService)ac.getBean("personService");
String name = ps.selectNameByPhone("15338597777");
System.out.println(name);
}
}
10.修改CalllogController的findAll控制器對應的findAll方法 ----------------------------------------------------------
/**
* 查詢所有的calllog
* 全表掃描
* @return
*/
public List<Calllog> findAll() {
List<Calllog> list = new ArrayList<Calllog>();
try {
//掃描
Scan scan = new Scan();
ResultScanner rs = table.getScanner(scan);
Iterator<Result> it = rs.iterator();
byte[] famliy = Bytes.toBytes("f1");
byte[] callerf = Bytes.toBytes("caller");
byte[] calleef = Bytes.toBytes("callee");
byte[] callTimef = Bytes.toBytes("callTime");
byte[] callDurationf = Bytes.toBytes("callDuration");
Calllog calllog = null;
while (it.hasNext()) {
Result next = it.next();
String caller = Bytes.toString(next.getValue(famliy, callerf));
String callee = Bytes.toString(next.getValue(famliy, calleef));
String callTime = Bytes.toString(next.getValue(famliy, callTimef));
String callDuration = Bytes.toString(next.getValue(famliy, callDurationf));
calllog = new Calllog();
//rowkey
String rowkey = Bytes.toString(next.getRow());
String flag = rowkey.split(",")[3];
calllog.setFlag(flag.equals("0")?true:false);
calllog.setCaller(caller);
calllog.setCallee(callee);
calllog.setCallTime(callTime);
calllog.setCallDuration(callDuration);
//查詢mysql設定主叫名字和被叫名字
String callerName = ps.selectNameByPhone(caller);
String calleeName = ps.selectNameByPhone(callee);
calllog.setCallerName(callerName);
calllog.setCalleeName(calleeName);
list.add(calllog);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
11.修改查詢全部的前端jsp介面[calllogList.jsp]
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>通話記錄</title>
<link rel="stylesheet" type="text/css" href="../css/my.css">
</head>
<body>
<table id="t1" border="1px" class="t-1" style="width: 800px">
<tr>
<td>本機號碼</td>
<td>本機名字</td>
<td>對方號碼</td>
<td>對方名字</td>
<td>是否主叫</td>
<td>通話時間</td>
<td>通話時長</td>
</tr>
<c:forEach items="${calllogs}" var="u">
<tr>
<td><c:out value="${u.caller}"/></td>
<td><c:out value="${u.callerName}"/></td>
<td><c:out value="${u.callee}"/></td>
<td><c:out value="${u.calleeName}"/></td>
<td>
<c:if test="${u.caller != param.caller}">
被叫
</c:if>
<c:if test="${u.caller == param.caller}">
主叫
</c:if>
</td>
<td><c:out value="${u.callTime}"/></td>
<td><c:out value="${u.callDuration}"/></td>
</tr>
</c:forEach>
<tr>
<td colspan="5" style="text-align: right">
</td>
</tr>
</table>
</body>
</html>
三、MR執行引數配置,關閉實體記憶體和虛擬記憶體對容器的限制 -- 解決MR作業啟動不起來的問題 -------------------------------------------------------------------------------- 預設限制是開啟的,最多分配給容器8G的實體記憶體,虛擬記憶體是實體記憶體的2.1倍。 [yarn-site.xml]
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>8192</value>
</property>
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
</property>
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>
<property>
<name>yarn.nodemanager.vmem-pmem-ratio</name>
<value>2.1</value>
</property>
四、實現web介面實時重新整理通話記錄的功能 ------------------------------------------------- 1.實現動態產生通話日誌,前端實時重新整理獲取 2.重寫日誌生成模組 a.將程式碼選項外部化,通過配置檔案[gendata.conf]動態載入 log.file=/home/ubuntu/calllog/calllog.log call.duration.max=600 call.duration.format=000 call.year=2018 call.time.format=yyyy/MM/dd HH:mm:ss gen.data.interval.ms=5000 b.新建工具類PropertiesUtil --------------------------------
package calllog.gen.main;
import java.io.InputStream;
import java.util.Properties;
/**
*
*/
public class PropertiesUtil {
static Properties prop ;
static{
try {
InputStream in = ClassLoader.getSystemResourceAsStream("gendata.conf");
prop = new Properties();
prop.load(in);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getProp(String key){
return prop.getProperty(key) ;
}
public static String getString(String key){
return prop.getProperty(key) ;
}
public static int getInt(String key){
return Integer.parseInt(prop.getProperty(key)) ;
}
}
c.重寫app類 ---------------------------------------
package calllog.gen.main;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
public class App {
public static Random random = new Random();
//電話簿
public static Map<String, String> callers = new HashMap<String, String>();
//電話號碼
public static List<String> phoneNumbers = new ArrayList<String>();
static{
callers.put("15811111111", "史讓");
callers.put("18022222222", "趙嗄");
callers.put("15133333333", "張錒 ");
callers.put("13269364444", "王以");
callers.put("15032295555", "張噢");
callers.put("17731086666", "張類");
callers.put("15338597777", "李平");
callers.put("15733218888", "杜跑");
callers.put("15614209999", "任陽");
callers.put("15778421111", "樑鵬");
callers.put("18641241111", "郭彤");
callers.put("15732641111", "劉飛");
callers.put("13341101111", "段星");
callers.put("13560191111", "唐華");
callers.put("18301581111", "楊謀");
callers.put("13520401111", "溫英");
callers.put("18332561111", "朱寬");
callers.put("18620191111", "劉宗");
phoneNumbers.addAll(callers.keySet());
}
public static void main(String [] args)
{
genCallLog();
}
/**
* 生成通話日誌
*/
private static void genCallLog() {
try {
//檔案寫入器
FileWriter fw = new FileWriter(PropertiesUtil.getString("log.file"), true);
while (true) {
//主叫
String caller = phoneNumbers.get(random.nextInt(callers.size()));
String callerName = callers.get(caller);
//被叫 (!= 主叫)
String callee = phoneNumbers.get(random.nextInt(callers.size()));
while (callee.equals(caller)) {
callee = phoneNumbers.get(random.nextInt(callers.size()));
}
String calleeName = callers.get(callee);
//通話時長(<10min)
int duration = random.nextInt(PropertiesUtil.getInt("call.duration.max")) + 1;
DecimalFormat df = new DecimalFormat();
df.applyPattern(PropertiesUtil.getString("call.duration.format"));
String dur = df.format(duration);
//通話時間timeStr
int year = PropertiesUtil.getInt("call.year");;
int month = random.nextInt(12);
int day = random.nextInt(29) + 1;
int hour = random.nextInt(24);
int min = random.nextInt(60);
int sec = random.nextInt(60);
Calendar calendar = Calendar.getInstance();
calendar.set(year,month,day,hour,min,sec);
Date date = calendar.getTime();
//如果時間超過今天就重新qushijian取時間.
Date now = new Date();
if (date.compareTo(now) > 0) {
continue ;
}
SimpleDateFormat dfs = new SimpleDateFormat();
dfs.applyPattern(PropertiesUtil.getString("call.time.format"));
String timeStr = dfs.format(date);
//通話日誌
//String callLog = caller + "," + callerName + "," + callee + "," + calleeName + "," + timeStr + "," + dur;
String callLog = caller + "," + callee + "," + timeStr + "," + dur;
fw.write(callLog+ "\r\n");
fw.flush();
System.out.println("callLog: " + callLog);
Thread.sleep(PropertiesUtil.getInt("gen.data.interval.ms"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
d.使用maven打包,並拷貝到/home/ubuntu/calllog目錄下 e.執行 ./calllog.sh,檢視calllog.log的生成情況 3.ajax:java指令碼的非同步訪問 + xml a.ssm模組中引入fastjson b.新增maven依賴 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency> c.模擬最底層的request和response,實現直接返回json串到前端頁面 1)編寫CalllogController.findAllJson
/**
* 模擬最底層的request和response,實現直接返回json串到前端頁面
*/
@RequestMapping("calllog/json/findAll")
public String findAllJson(HttpServletResponse response)
{
try {
List<Calllog> list = cs.findAll();
String jsonStr = JSONArray.toJSONString(list);
//設定迴應的資料型別是json串
response.setContentType("application/json");
//得到傳送給客戶端的輸出流
ServletOutputStream sos = response.getOutputStream();
sos.write(jsonStr.getBytes());
sos.flush();
sos.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
2)執行ssm app,開啟網址http://localhost:8080/calllog/json/findAll測試 d.web整合jQuery庫,實現ajax訪問後臺資料,實現動態重新整理 1)引入web/js/jquery-3.2.0.min.js 2)修改calllogList.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>通話記錄</title>
<link rel="stylesheet" type="text/css" href="../css/my.css">
<script type="text/javascript" src="../js/jquery-3.2.0.min.js"></script>
<script type="text/javascript" >
//定義函式
function refreshTable(){
$("#t1 tbody").empty();
$.getJSON("/calllog/json/findAll", function (data) {
$.each(data, function (i, obj) {
var str = "<tr><td>" + obj.caller + "</td>";
str = str + "<td> " + obj.callerName + "</td>";
str = str + "<td> " + obj.callee + "</td>";
str = str + "<td> " + obj.calleeName + "</td>";
str = str + "<td></td>";
str = str + "<td> " + obj.callTime + "</td>";
str = str + "<td> " + obj.callDuration + "</td>";
str = str + "</tr>";
$("#t1 tbody").append(str);
});
});
}
$(function(){
setInterval(refreshTable, 2000);
})
</script>
</head>
<body>
<input id="btnCleanTable" type="button" value="清除表格"><br>
<table id="t1" border="1px" class="t-1" style="width: 800px">
<thead>
<tr>
<td>本機號碼</td>
<td>本機名字</td>
<td>對方號碼</td>
<td>對方名字</td>
<td>是否主叫</td>
<td>通話時間</td>
<td>通話時長</td>
</tr>
</thead>
<tbody>
<c:forEach items="${calllogs}" var="u">
<tr>
<td><c:out value="${u.caller}"/></td>
<td><c:out value="${u.callerName}"/></td>
<td><c:out value="${u.callee}"/></td>
<td><c:out value="${u.calleeName}"/></td>
<td>
<c:if test="${u.caller != param.caller}">
被叫
</c:if>
<c:if test="${u.caller == param.caller}">
主叫
</c:if>
</td>
<td><c:out value="${u.callTime}"/></td>
<td><c:out value="${u.callDuration}"/></td>
</tr>
</c:forEach>
<tr>
<td colspan="7" style="text-align: right">
</td>
</tr>
</tbody>
</table>
</body>
</html>