content: package com.sohu.cache.web.controller;
  
  import com.alibaba.fastjson.JSONObject;
  import com.sohu.cache.constant.AppAuditType;
  import com.sohu.cache.constant.AppStatusEnum;
  import com.sohu.cache.constant.AppUserTypeEnum;
  import com.sohu.cache.constant.TimeDimensionalityEnum;
  import com.sohu.cache.entity.*;
  import com.sohu.cache.stats.app.AppDailyDataCenter;
  import com.sohu.cache.stats.app.AppDeployCenter;
  import com.sohu.cache.stats.app.AppStatsCenter;
  import com.sohu.cache.stats.instance.InstanceStatsCenter;
  import com.sohu.cache.util.ConstUtils;
  import com.sohu.cache.util.DemoCodeUtil;
  import com.sohu.cache.web.vo.AppDetailVO;
  import com.sohu.cache.web.chart.model.HighchartPoint;
  import com.sohu.cache.web.chart.model.SimpleChartData;
  import com.sohu.cache.web.enums.SuccessEnum;
  import com.sohu.cache.web.util.AppEmailUtil;
  import com.sohu.cache.web.util.DateUtil;
  import com.sohu.cache.web.util.Page;
  
  import net.sf.json.JSONArray;
  
  import org.apache.commons.collections.CollectionUtils;
  import org.apache.commons.lang.StringUtils;
  import org.apache.commons.lang.math.NumberUtils;
  import org.apache.commons.lang.time.DateUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.stereotype.Controller;
  import org.springframework.ui.Model;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RequestMethod;
  import org.springframework.web.servlet.ModelAndView;
  
  import javax.annotation.Resource;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  
  import java.sql.Timestamp;
  import java.text.ParseException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.concurrent.TimeUnit;
  
  /**
   * 应用统计相关
   *
   * @author leifu
   * @Time 2014年8月31日
   */
  @Controller
  @RequestMapping("/admin/app")
  public class AppController extends BaseController {
      private Logger logger = LoggerFactory.getLogger(AppController.class);
      
      @Resource(name = "appStatsCenter")
      private AppStatsCenter appStatsCenter;
  
      @Resource(name = "appEmailUtil")
      private AppEmailUtil appEmailUtil;
  
      @Resource(name = "appDeployCenter")
      private AppDeployCenter appDeployCenter;
      
      @Resource(name = "instanceStatsCenter")
      private InstanceStatsCenter instanceStatsCenter;
      
      @Resource(name = "appDailyDataCenter")
      private AppDailyDataCenter appDailyDataCenter;
      
      /**
       * 初始化贡献者页面
       * @return
       */
      @RequestMapping("/initBecomeContributor")
      public ModelAndView doInitBecomeContributor(HttpServletRequest request,
                          HttpServletResponse response, Model model){
          model.addAttribute("currentUser", getUserInfo(request));
          return new ModelAndView("app/initBecomeContributor");
      }
      
      /**
       * 成为cachecloud贡献者
       * @param groupName 项目组
       * @param applyReason 申请理由
       * @return
       */
      @RequestMapping("/addBecomeContributor")
      public ModelAndView doAddBecomeContributor(HttpServletRequest request,
                          HttpServletResponse response, Model model, String groupName, String applyReason){
          appEmailUtil.noticeBecomeContributor(groupName, applyReason, getUserInfo(request));
          model.addAttribute("success", SuccessEnum.SUCCESS.value());
          return new ModelAndView("");    
      }
      
      /**
       * 单个应用首页
       *
       * @param appId
       * @param tabTag       标签名
       * @param firstCommand 第一条命令
       * @return
       * @throws ParseException
       */
      @RequestMapping("/index")
      public ModelAndView index(HttpServletRequest request,
                                HttpServletResponse response, Model model, Long appId, String tabTag, String firstCommand)
              throws ParseException {
          // 如果应用id为空，取第一个应用id
          if (appId == null) {
              return new ModelAndView("redirect:/admin/app/list");
          }
          
          // 日期转换
          String startDateParam = request.getParameter("startDate");
          String endDateParam = request.getParameter("endDate");
          if (StringUtils.isBlank(startDateParam) || StringUtils.isBlank(endDateParam)) {
              Date startDate = new Date();
              startDateParam = DateUtil.formatDate(startDate, "yyyy-MM-dd");
              endDateParam = DateUtil.formatDate(DateUtils.addDays(startDate, 1), "yyyy-MM-dd");
          }
          
          //慢查询
          String slowLogStartDateParam = request.getParameter("slowLogStartDate");
          String slowLogEndDateParam = request.getParameter("slowLogEndDate");
          if (StringUtils.isBlank(slowLogStartDateParam) || StringUtils.isBlank(slowLogEndDateParam)) {
              Date startDate = new Date();
              slowLogStartDateParam = DateUtil.formatDate(startDate, "yyyy-MM-dd");
              slowLogEndDateParam = DateUtil.formatDate(DateUtils.addDays(startDate, 1), "yyyy-MM-dd");
          }
          
          //日报
          String dailyDateParam = request.getParameter("dailyDate");
          if (StringUtils.isBlank(dailyDateParam)) {
              dailyDateParam = DateUtil.formatDate(DateUtils.addDays(new Date(), -1), "yyyy-MM-dd");
          }
          
          model.addAttribute("startDate", startDateParam);
          model.addAttribute("endDate", endDateParam);
          model.addAttribute("slowLogStartDate", slowLogStartDateParam);
          model.addAttribute("slowLogEndDate", slowLogEndDateParam);
          model.addAttribute("dailyDate", dailyDateParam);
          model.addAttribute("appId", appId);
          model.addAttribute("tabTag", tabTag);
          model.addAttribute("firstCommand", firstCommand);
          
  
          return new ModelAndView("app/userAppsIndex");
  
      }
  
      
      /**
       * 应用统计相关
       */
      @RequestMapping("/stat")
      public ModelAndView appStat(HttpServletRequest request,
                                  HttpServletResponse response, Model model, Long appId) throws ParseException {
          // 1.获取app的VO
          AppDetailVO appDetail = appStatsCenter.getAppDetail(appId);
          model.addAttribute("appDetail", appDetail);
  
          // 2. 时间
          TimeBetween timeBetween = getTimeBetween(request, model, "startDate", "endDate");
          long beginTime = timeBetween.getStartTime();
          long endTime = timeBetween.getEndTime();
          
          // 3.是否超过1天
          if (endTime - beginTime > TimeUnit.DAYS.toMillis(1)) {
              model.addAttribute("betweenOneDay", 0);
          } else {
              model.addAttribute("betweenOneDay", 1);
          }
  
          // 4. top5命令
          List<AppCommandStats> top5Commands = appStatsCenter.getTopLimitAppCommandStatsList(appId, beginTime, endTime, 5);
          model.addAttribute("top5Commands", top5Commands);
          
          // 5.峰值
          List<AppCommandStats> top5ClimaxList = new ArrayList<AppCommandStats>();
          if (CollectionUtils.isNotEmpty(top5Commands)) {
              for (AppCommandStats appCommandStats : top5Commands) {
                  AppCommandStats temp = appStatsCenter.getCommandClimax(appId, beginTime, endTime, appCommandStats.getCommandName());
                  if (temp != null) {
                      top5ClimaxList.add(temp);
                  }
              }
          }
          model.addAttribute("top5ClimaxList", top5ClimaxList);
  
          model.addAttribute("appId", appId);
          return new ModelAndView("app/appStat");
      }
      
      /**
       * 命令曲线
       * @param firstCommand 第一条命令
       */
      @RequestMapping("/commandAnalysis")
      public ModelAndView appCommandAnalysis(HttpServletRequest request,
                                             HttpServletResponse response, Model model, Long appId, String firstCommand) throws ParseException {
          // 1.获取app的VO
          AppDetailVO appDetail = appStatsCenter.getAppDetail(appId);
          model.addAttribute("appDetail", appDetail);
  
          // 2.返回日期
          TimeBetween timeBetween = getTimeBetween(request, model, "startDate", "endDate");
  
          // 3.是否超过1天
          if (timeBetween.getEndTime() - timeBetween.getStartTime() > TimeUnit.DAYS.toMillis(1)) {
              model.addAttribute("betweenOneDay", 0);
          } else {
              model.addAttribute("betweenOneDay", 1);
          }
  
          // 4.获取top命令
          List<AppCommandStats> allCommands = appStatsCenter.getTopLimitAppCommandStatsList(appId, timeBetween.getStartTime(), timeBetween.getEndTime(), 20);
          model.addAttribute("allCommands", allCommands);
          if (StringUtils.isBlank(firstCommand) && CollectionUtils.isNotEmpty(allCommands)) {
              model.addAttribute("firstCommand", allCommands.get(0).getCommandName());
          } else {
              model.addAttribute("firstCommand", firstCommand);
          }
          model.addAttribute("appId", appId);
          // 返回标签名
          return new ModelAndView("app/appCommandAnalysis");
      }
  
      /**
       * 应用故障
       */
      @RequestMapping("/fault")
      public ModelAndView appFault(HttpServletRequest request,
                                   HttpServletResponse response, Model model) {
  
          return new ModelAndView("app/appFault");
      }
  
      /**
       * 应用拓扑图
       *
       * @param appId
       * @return
       */
      @RequestMapping("/topology")
      public ModelAndView statTopology(HttpServletRequest request,
                                       HttpServletResponse response, Long appId, Model model) {
          //应用信息
          AppDesc appDesc = appService.getByAppId(appId);
          model.addAttribute("appDesc", appDesc);
          //实例相关信息(包含统计)
          fillAppInstanceStats(appId, model);
          return new ModelAndView("app/appTopology");
      }
      
      /**
       * 应用机器拓扑图
       *
       * @param appId
       * @return
       */
      @RequestMapping("/machineInstancesTopology")
      public ModelAndView machineInstancesTopology(HttpServletRequest request,
                                       HttpServletResponse response, Long appId, Model model) {
          //应用信息
          AppDesc appDesc = appService.getByAppId(appId);
          model.addAttribute("appDesc", appDesc);
          //拓扑
          fillAppMachineInstanceTopology(appId, model);
          return new ModelAndView("app/appMachineInstancesTopology");
      }
  
      /**
       * 应用基本信息
       *
       * @param appId 应用id
       */
      @RequestMapping("/detail")
      public ModelAndView appDetail(HttpServletRequest request,
                                    HttpServletResponse response, Model model, Long appId) {
          // 获取应用vo
          AppDetailVO appDetail = appStatsCenter.getAppDetail(appId);
          model.addAttribute("appDetail", appDetail);
          return new ModelAndView("app/appDetail");
      }
  
      /**
       * 获取某个命令时间分布图
       *
       * @param appId 应用id
       * @throws ParseException
       */
      @RequestMapping("/getCommandStats")
      public ModelAndView getCommandStats(HttpServletRequest request,
                                          HttpServletResponse response, Model model, Long appId) throws ParseException {
          TimeBetween timeBetween = getJsonTimeBetween(request);
          long beginTime = timeBetween.getStartTime();
          long endTime = timeBetween.getEndTime();
          // 命令参数
          String commandName = request.getParameter("commandName");
          List<AppCommandStats> appCommandStatsList;
          if (StringUtils.isNotBlank(commandName)) {
              appCommandStatsList = appStatsCenter.getCommandStatsList(appId, beginTime, endTime, commandName);
          } else {
              appCommandStatsList = appStatsCenter.getCommandStatsList(appId, beginTime, endTime);
          }
          String result = assembleJson(appCommandStatsList);
          write(response, result);
          return null;
      }
      
      /**
       * 获取某个命令时间分布图
       *
       * @param appId 应用id
       * @throws ParseException
       */
      @RequestMapping("/getMutiDatesCommandStats")
      public ModelAndView getMutiDatesCommandStats(HttpServletRequest request,
                                          HttpServletResponse response, Model model, Long appId) throws ParseException {
          TimeBetween timeBetween = getJsonTimeBetween(request);
          // 命令参数
          String commandName = request.getParameter("commandName");
          List<AppCommandStats> appCommandStatsList;
          if (StringUtils.isNotBlank(commandName)) {
              appCommandStatsList = appStatsCenter.getCommandStatsListV2(appId, timeBetween.getStartTime(), timeBetween.getEndTime(), TimeDimensionalityEnum.MINUTE, commandName);
          } else {
              appCommandStatsList = appStatsCenter.getCommandStatsListV2(appId, timeBetween.getStartTime(), timeBetween.getEndTime(), TimeDimensionalityEnum.MINUTE);
          }
          String result = assembleMutilDateAppCommandJsonMinute(appCommandStatsList, timeBetween.getStartDate(), timeBetween.getEndDate());
          model.addAttribute("data", result);
          return new ModelAndView("");
      }
  
      
      /**
       * 获取命中率、丢失率等分布
       *
       * @param appId    应用id
       * @param statName 统计项(hit,miss等)
       * @throws ParseException
       */
      @RequestMapping("/getAppStats")
      public ModelAndView getAppStats(HttpServletRequest request,
                                      HttpServletResponse response, Model model, Long appId,
                                      String statName) throws ParseException {
          TimeBetween timeBetween = getJsonTimeBetween(request);
          List<AppStats> appStats = appStatsCenter.getAppStatsListByMinuteTime(appId, timeBetween.getStartTime(), timeBetween.getEndTime());
          String result = assembleAppStatsJson(appStats, statName);
          write(response, result);
          return null;
      }
  
      /**
       * 多命令
       * @param appId
       * @param statName
       * @return
       * @throws ParseException
       */
      @RequestMapping("/getMutiStatAppStats")
      public ModelAndView getMutiStatAppStats(HttpServletRequest request,
                                      HttpServletResponse response, Model model, Long appId) throws ParseException {
          String statNames = request.getParameter("statName");
          List<String> statNameList = Arrays.asList(statNames.split(ConstUtils.COMMA));
          TimeBetween timeBetween = getJsonTimeBetween(request);
          List<AppStats> appStats = appStatsCenter.getAppStatsList(appId, timeBetween.getStartTime(), timeBetween.getEndTime(), TimeDimensionalityEnum.MINUTE);
          String result = assembleMutiStatAppStatsJsonMinute(appStats, statNameList, timeBetween.getStartDate());
          model.addAttribute("data", result);
          return new ModelAndView("");
      }
  
      /**
       * 获取命中率、丢失率等分布
       *
       * @param appId    应用id
       * @param statName 统计项(hit,miss等)
       * @throws ParseException
       */
      @RequestMapping("/getMutiDatesAppStats")
      public ModelAndView getMutiDatesAppStats(HttpServletRequest request,
                                      HttpServletResponse response, Model model, Long appId,
                                      String statName, Integer addDay) throws ParseException {
          TimeBetween timeBetween = getJsonTimeBetween(request);
          List<AppStats> appStats = appStatsCenter.getAppStatsList(appId, timeBetween.getStartTime(), timeBetween.getEndTime(), TimeDimensionalityEnum.MINUTE);
          String result = assembleMutilDateAppStatsJsonMinute(appStats, statName, timeBetween.getStartDate(), timeBetween.getEndDate());
          model.addAttribute("data", result);
          return new ModelAndView("");
      }
      
      /**
       * 获取指定时间内某个应用全部实例的统计信息
       * @param appId
       */
      @RequestMapping("/appInstanceNetStat")
      public ModelAndView appInstanceNetStat(HttpServletRequest request, HttpServletResponse response, Model model, Long appId) throws ParseException {
          // 应用基本信息
          AppDesc appDesc = appService.getByAppId(appId);
          model.addAttribute("appDesc", appDesc);
          model.addAttribute("appId", appId);
  
          // 日期格式转换
          getTimeBetween(request, model, "startDate", "endDate");
          
          return new ModelAndView("app/appInstanceNetStat");
      }
      
      
      
      /**
       * 获取指定时间内某个应用全部实例的统计信息
       * @param appId 应用流量
       */
      @RequestMapping("/getAppInstancesNetStat")
      public ModelAndView getAppInstancesNetStat(HttpServletRequest request, HttpServletResponse response, Model model, Long appId) throws ParseException {
          //时间转换
          TimeBetween timeBetween = getJsonTimeBetween(request);
          
          //缩减字段
          String netInCommand = "total_net_input_bytes";
          String netOutCommand = "total_net_output_bytes";
          Map<String,String> commandMap = new HashMap<String, String>();
          commandMap.put(netInCommand, "i");
          commandMap.put(netOutCommand, "o");
          
          //获取应用下所有实例网络流量统计
          Map<Integer, Map<String, List<InstanceCommandStats>>> appInstancesNetStat = instanceStatsCenter
                  .getStandardStatsList(appId, timeBetween.getStartTime(), timeBetween.getEndTime(),
                          Arrays.asList(netInCommand, netOutCommand));
  
          //解析成json数组
          List<Map<String, Object>> appInstancesNetStatList = new ArrayList<Map<String, Object>>();
          for (Entry<Integer, Map<String, List<InstanceCommandStats>>> entry : appInstancesNetStat.entrySet()) {
              Integer instanceId = entry.getKey();
              
              //实例基本信息
              Map<String, Object> instanceStatMap = new HashMap<String, Object>();
              instanceStatMap.put("instanceId", instanceId);
              InstanceInfo instanceInfo = instanceStatsCenter.getInstanceInfo(instanceId);
              instanceStatMap.put("instanceInfo", instanceInfo.getIp() + ":" + instanceInfo.getPort());
              
              //每个实例的统计信息
              List<Map<String, Object>> instanceNetStatMapList = new ArrayList<Map<String, Object>>();
              instanceStatMap.put("instanceNetStatMapList", instanceNetStatMapList);
              appInstancesNetStatList.add(instanceStatMap);
  
              //记录输入和输出流量
              Map<String, List<InstanceCommandStats>> map = entry.getValue();
              List<InstanceCommandStats> instanceCommandStatsList = new ArrayList<InstanceCommandStats>();
              instanceCommandStatsList.addAll(map.get(netInCommand));
              instanceCommandStatsList.addAll(map.get(netOutCommand));
  
              Map<Long, Map<String, Object>> total = new HashMap<Long, Map<String, Object>>();
              for (InstanceCommandStats instanceCommandStat : instanceCommandStatsList) {
                  //用timestamp作为key,保证输入和输出流量在一个Map统计里
                  long timestamp = instanceCommandStat.getTimeStamp();
                  long commandCount = instanceCommandStat.getCommandCount();
                  String command = instanceCommandStat.getCommandName();
                  //精简字段
                  command = commandMap.get(command);
                  if (total.containsKey(timestamp)) {
                      Map<String,Object> tmpMap = total.get(timestamp);
                      tmpMap.put(command, commandCount);
                  } else {
                      Map<String,Object> tmpMap = new HashMap<String, Object>();
                      tmpMap.put("t", timestamp);
                      tmpMap.put(command, commandCount);
                      total.put(timestamp, tmpMap);
                      instanceNetStatMapList.add(tmpMap);
                  }
              }
          }
          
          String result = JSONObject.toJSONString(appInstancesNetStatList);
          write(response, result);
          return null;
      }
      
  
      /**
       *
       * @param appId
       * @throws ParseException
       */
      @RequestMapping("/getTop5Commands")
      public ModelAndView getAppTop5Commands(HttpServletRequest request,
                                             HttpServletResponse response, Model model, Long appId) throws ParseException {
          TimeBetween timeBetween = getJsonTimeBetween(request);
          List<AppCommandStats> appCommandStats = appStatsCenter.getTop5AppCommandStatsList(appId, timeBetween.getStartTime(), timeBetween.getEndTime());
          String result = assembleJson(appCommandStats);
          write(response, result);
          return null;
      }
  
      /**
       * 应用各个命令分布情况
  
       *
       * @param appId 应用id
       * @throws ParseException
       */
      @RequestMapping("/appCommandDistribute")
      public ModelAndView appCommandDistribute(HttpServletRequest request,
                                               HttpServletResponse response, Model model, Long appId) throws ParseException {
          TimeBetween timeBetween = getJsonTimeBetween(request);
          List<AppCommandGroup> appCommandGroupList = appStatsCenter.getAppCommandGroup(appId, timeBetween.getStartTime(), timeBetween.getEndTime());
          String result = assembleGroupJson(appCommandGroupList);
          write(response, result);
          return null;
      }
      
      
      /**
       * 应用列表
       *
       * @param userId
       * @return
       */
      @RequestMapping(value = "/list")
      public ModelAndView doAppList(HttpServletRequest request,
                                    HttpServletResponse response, Model model, AppSearch appSearch) {
          // 1.获取该用户能够读取的应用列表,没有返回申请页面
          AppUser currentUser = getUserInfo(request);
          model.addAttribute("currentUser", currentUser);
          int userAppCount = appService.getUserAppCount(currentUser.getId());
          if (userAppCount == 0 && !AppUserTypeEnum.ADMIN_USER.value().equals(currentUser.getType())) {
              return new ModelAndView("redirect:/admin/app/init");
          }
          // 默认只出运行中的
          if (appSearch.getAppStatus() == null) {
              appSearch.setAppStatus(AppStatusEnum.STATUS_PUBLISHED.getStatus());
          }
          // 2.1 分页相关
          int totalCount = appService.getAppDescCount(currentUser, appSearch);
          int pageNo = NumberUtils.toInt(request.getParameter("pageNo"), 1);
          int pageSize = NumberUtils.toInt(request.getParameter("pageSize"), 10);
          Page page = new Page(pageNo,pageSize, totalCount);
          model.addAttribute("page", page);
  
          // 2.2 查询指定时间客户端异常
          appSearch.setPage(page);
          List<AppDesc> apps = appService.getAppDescList(currentUser, appSearch);
          // 2.3 应用列表
          List<AppDetailVO> appDetailList = new ArrayList<AppDetailVO>();
          model.addAttribute("appDetailList", appDetailList);
  
          // 3. 全局统计
          long totalApplyMem = 0;
          long totalUsedMem = 0;
          long totalApps = 0;
          if (apps != null && apps.size() > 0) {
              for (AppDesc appDesc : apps) {
                  AppDetailVO appDetail = appStatsCenter.getAppDetail(appDesc.getAppId());
                  appDetailList.add(appDetail);
                  totalApplyMem += appDetail.getMem();
                  totalUsedMem += appDetail.getMemUsePercent() * appDetail.getMem() / 100.0;
                  totalApps++;
              }
          }
          model.addAttribute("totalApps", totalApps);
          model.addAttribute("totalApplyMem", totalApplyMem);
          model.addAttribute("totalUsedMem", totalUsedMem);
  
          return new ModelAndView("app/appList");
      }
  
      /**
       * 初始化应用申请
       */
      @RequestMapping(value = "/init")
      public ModelAndView doAppInit(HttpServletRequest request,
                                    HttpServletResponse response, Model model) {
          return new ModelAndView("app/appInit");
      }
  
      /**
       * 添加应用
       *
       * @param appDesc 应用实体
       * @param memSize 申请容量(G)
       * @return
       */
      @RequestMapping(value = "/add", method = RequestMethod.POST)
      public ModelAndView doAppAdd(HttpServletRequest request,
                                   HttpServletResponse response, Model model, AppDesc appDesc, String memSize) {
          AppUser appUser = getUserInfo(request);
          if (appDesc != null) {
              Timestamp now = new Timestamp(new Date().getTime());
              appDesc.setCreateTime(now);
              appDesc.setPassedTime(now);
              appDesc.setVerId(1);
              appDesc.setStatus((short) AppStatusEnum.STATUS_ALLOCATED.getStatus());
              appDeployCenter.createApp(appDesc, appUser, memSize);
          }
          return new ModelAndView("redirect:/admin/app/list");
      }
  
      /**
       * 查看应用名是否存在
       *
       * @param appName
       * @return
       */
      @RequestMapping(value = "/checkAppNameExist")
      public ModelAndView doCheckAppNameExist(HttpServletRequest request,
                                              HttpServletResponse response, Model model, String appName) {
          AppDesc appDesc = appService.getAppByName(appName);
          if (appDesc != null) {
              write(response, String.valueOf(SuccessEnum.SUCCESS.value()));
          } else {
              write(response, String.valueOf(SuccessEnum.FAIL.value()));
          }
          return null;
      }
  
      /**
       * 应用命令查询
       *
       * @param appId
       * @return
       */
      @RequestMapping("/command")
      public ModelAndView command(HttpServletRequest request, HttpServletResponse response, Model model, Long appId) {
          if (appId != null && appId > 0) {
              model.addAttribute("appId", appId);
          }
          return new ModelAndView("app/appCommand");
      }
  
      /**
       * 执行应用命令
       *
       * @param appId
       * @return
       */
      @RequestMapping("/commandExecute")
      public ModelAndView commandExecute(HttpServletRequest request, HttpServletResponse response, Model model, Long appId) {
          if (appId != null && appId > 0) {
              model.addAttribute("appId", appId);
              String command = request.getParameter("command");
              String result = appStatsCenter.executeCommand(appId, command);
              model.addAttribute("result", result);
          } else {
              model.addAttribute("result", "error");
          }
          return new ModelAndView("app/commandExecute");
      }
  
      /**
       * 删除应用下的指定用户
       *
       * @param userId
       * @param appId
       * @return
       */
      @RequestMapping(value = "/deleteAppToUser")
      public ModelAndView doDeleteAppToUser(HttpServletRequest request,
                                            HttpServletResponse response, Model model, Long userId, Long appId) {
          if (userId != null && appId != null) {
              // 验证删除权限
              AppUser currentUser = getUserInfo(request);
              List<AppToUser> appToUsers = appService.getAppToUserList(appId);
              if (CollectionUtils.isNotEmpty(appToUsers)) {
                  for (AppToUser appToUser : appToUsers) {
                      if (appToUser.getUserId().equals(currentUser.getId())) {
                          write(response, String.valueOf(SuccessEnum.FAIL.value()));
                      }
                  }
              }
              appService.deleteAppToUser(appId, userId);
              write(response, String.valueOf(SuccessEnum.SUCCESS.value()));
          } else {
              write(response, String.valueOf(SuccessEnum.FAIL.value()));
          }
          return null;
      }
      
      /**
       * 更新用户
       * @param name
       * @param chName
       * @param email
       * @param mobile
       * @param type
       * @param userId
       * @return
       */
      @RequestMapping(value = "/changeAppUserInfo")
      public ModelAndView doAddUser(HttpServletRequest request,
              HttpServletResponse response, Model model, String name, String chName, String email, String mobile,
              Integer type, Long userId) {
          // 后台暂时不对参数进行验证
          AppUser appUser = AppUser.buildFrom(userId, name, chName, email, mobile, type);
          try {
              if (userId == null) {
                  userService.save(appUser);
              } else {
                  userService.update(appUser);
              }
              write(response, String.valueOf(SuccessEnum.SUCCESS.value()));
          } catch (Exception e) {
              write(response, String.valueOf(SuccessEnum.FAIL.value()));
              logger.error(e.getMessage(), e);
          }
          return null;
      }
      
  
      /**
       * 扩容申请
       *
       * @param appId          应用id
       * @param applyMemSize   申请容量
       * @param appScaleReason 申请原因
       * @return
       */
      @RequestMapping(value = "/scale")
      public ModelAndView doScaleApp(HttpServletRequest request,
                                     HttpServletResponse response, Model model, Long appId, String applyMemSize, String appScaleReason) {
          AppUser appUser = getUserInfo(request);
          AppDesc appDesc = appService.getByAppId(appId);
          AppAudit appAudit = appService.saveAppScaleApply(appDesc, appUser, applyMemSize, appScaleReason, AppAuditType.APP_SCALE);
          appEmailUtil.noticeAppResult(appDesc, appAudit);
          write(response, String.valueOf(SuccessEnum.SUCCESS.value()));
          return null;
      }
  
      /**
       * 应用修改配置申请
       *
       * @param appId          应用id
       * @param appConfigKey   配置项
       * @param appConfigValue 配置值
       * @return
       */
      @RequestMapping(value = "/changeAppConfig")
      public ModelAndView doChangeAppConfig(HttpServletRequest request,
                                            HttpServletResponse response, Model model, Long appId, Long instanceId, String appConfigKey, String appConfigValue, String appConfigReason) {
          AppUser appUser = getUserInfo(request);
          AppDesc appDesc = appService.getByAppId(appId);
          AppAudit appAudit = appService.saveAppChangeConfig(appDesc, appUser, instanceId, appConfigKey, appConfigValue,appConfigReason, AppAuditType.APP_MODIFY_CONFIG);
          appEmailUtil.noticeAppResult(appDesc, appAudit);
          write(response, String.valueOf(SuccessEnum.SUCCESS.value()));
          return null;
      }
      
      /**
       * 实例修改配置申请
       *
       * @param appId          应用id
       * @param appConfigKey   配置项
       * @param appConfigValue 配置值
       * @return
       */
      @RequestMapping(value = "/changeInstanceConfig")
      public ModelAndView doChangeInstanceConfig(HttpServletRequest request,
                                            HttpServletResponse response, Model model, Long appId, Long instanceId, String instanceConfigKey, String instanceConfigValue, String instanceConfigReason) {
          AppUser appUser = getUserInfo(request);
          AppDesc appDesc = appService.getByAppId(appId);
          AppAudit appAudit = appService.saveInstanceChangeConfig(appDesc, appUser, instanceId, instanceConfigKey, instanceConfigValue, instanceConfigReason, AppAuditType.INSTANCE_MODIFY_CONFIG);
          appEmailUtil.noticeAppResult(appDesc, appAudit);
          write(response, String.valueOf(SuccessEnum.SUCCESS.value()));
          return null;
      }
  
      /**
       * 添加应用和用户对应关系
       *
       * @param appId    应用id
       * @param userName 用户名(邮箱前缀)
       * @return
       */
      @RequestMapping(value = "/addAppToUser")
      public ModelAndView doAddAppToUser(HttpServletRequest request,
                                         HttpServletResponse response, Model model, Long appId, String userName) {
          if (StringUtils.isNotBlank(userName)) {
              AppUser needAddAppUser = userService.getByName(userName);
              if (needAddAppUser != null) {
                  appService.saveAppToUser(appId, needAddAppUser.getId());
                  write(response, String.valueOf(SuccessEnum.SUCCESS.value()));
              } else {
                  write(response, String.valueOf(SuccessEnum.FAIL.value()));
              }
          }
          return null;
      }
  
      /**
       * 修改应用报警配置
       */
      @RequestMapping(value = "/changeAppAlertConfig")
      public ModelAndView doChangeAppAlertConfig(HttpServletRequest request,
                                                 HttpServletResponse response, Model model) {
  
          long appId = NumberUtils.toLong(request.getParameter("appId"), -1);
          int memAlertValue =  NumberUtils.toInt(request.getParameter("memAlertValue"), -1);
          int clientConnAlertValue =  NumberUtils.toInt(request.getParameter("clientConnAlertValue"), -1);
          SuccessEnum result = appService.changeAppAlertConfig(appId, memAlertValue,clientConnAlertValue, getUserInfo(request));
          write(response, String.valueOf(result.value()));
          return null;
      }
      
      /**
       * 修改应用信息
       */
      @RequestMapping(value = "/updateAppDetail")
      public ModelAndView doUpdateAppDetail(HttpServletRequest request,
                                                 HttpServletResponse response, Model model) {
          long appId = NumberUtils.toLong(request.getParameter("appId"), 0);
          AppUser appUser = getUserInfo(request);
          logger.warn("{} want to update appId={} info!", appUser.getName(), appId);
          String appDescName =  request.getParameter("appDescName");
          String appDescIntro =  request.getParameter("appDescIntro");
          String officer = request.getParameter("officer");
          SuccessEnum successEnum = SuccessEnum.SUCCESS;
          if (appId <= 0 || StringUtils.isBlank(appDescName) || StringUtils.isBlank(appDescIntro) || StringUtils.isBlank(officer)) {
              successEnum = SuccessEnum.FAIL;
          } else {
              try {
                  AppDesc appDesc = appService.getByAppId(appId);
                  appDesc.setName(appDescName);
                  appDesc.setIntro(appDescIntro);
                  appDesc.setOfficer(officer);
                  appService.update(appDesc);
              } catch (Exception e) {
                  logger.error(e.getMessage(), e);
                  successEnum = SuccessEnum.FAIL;
              }
          }
          write(response, String.valueOf(successEnum.value()));
          return null;
      }
      
  
      @RequestMapping(value = "/demo")
      public ModelAndView doDemo(HttpServletRequest request, HttpServletResponse response, Long appId, Model model) {
          if (appId != null && appId > 0) {
              AppDesc appDesc = appService.getByAppId(appId);
              List<String> code = DemoCodeUtil.getCode(appDesc.getType(), appDesc.getAppId());
              List<String> dependency = DemoCodeUtil.getDependencyRedis();
              List<String> springConfig = DemoCodeUtil.getSpringConfig(appDesc.getType(), appDesc.getAppId());
              String restApi = DemoCodeUtil.getRestAPI(appDesc.getType(), appDesc.getAppId());
              
              if(CollectionUtils.isNotEmpty(springConfig) && springConfig.size() > 0){
                  model.addAttribute("springConfig", springConfig);
              }
              model.addAttribute("dependency",dependency);
              model.addAttribute("code", code);
              model.addAttribute("status", 1);
              model.addAttribute("restApi", restApi);
          } else {
              model.addAttribute("status", 0);
          }
          return new ModelAndView("app/appDemo");
      }
  
      /**
       * 应用日报查询
       */
      @RequestMapping("/daily")
      public ModelAndView appDaily(HttpServletRequest request,
                                    HttpServletResponse response, Model model, Long appId) throws ParseException {
          // 1. 应用信息
          AppDesc appDesc = appService.getByAppId(appId);
          model.addAttribute("appDesc", appDesc);
  
          // 2. 日期
          String dailyDateParam = request.getParameter("dailyDate");
          Date date;
          if (StringUtils.isBlank(dailyDateParam)) {
              date = DateUtils.addDays(new Date(), -1);
          } else {
              date = DateUtil.parseYYYY_MM_dd(dailyDateParam);
          }
          model.addAttribute("dailyDate", dailyDateParam);
  
          // 3. 日报
          AppDailyData appDailyData = appDailyDataCenter.getAppDailyData(appId, date);
          model.addAttribute("appDailyData", appDailyData);
  
          return new ModelAndView("app/appDaily");
      }
  
      /**
       * 应用历史慢查询
       * @param appId
       * @return
       * @throws ParseException 
       */
      @RequestMapping("/slowLog")
      public ModelAndView appSlowLog(HttpServletRequest request,
                                    HttpServletResponse response, Model model, Long appId) throws ParseException {
          // 应用基本信息
          AppDesc appDesc = appService.getByAppId(appId);
          model.addAttribute("appDesc", appDesc);
          
          // 开始和结束日期
          TimeBetween timeBetween = getTimeBetween(request, model, "slowLogStartDate", "slowLogEndDate");
          Date startDate = timeBetween.getStartDate();
          Date endDate = timeBetween.getEndDate();
          
          // 应用慢查询日志
          Map<String,Long> appInstanceSlowLogCountMap = appStatsCenter.getInstanceSlowLogCountMapByAppId(appId, startDate, endDate);
          model.addAttribute("appInstanceSlowLogCountMap", appInstanceSlowLogCountMap);
          List<InstanceSlowLog> appInstanceSlowLogList = appStatsCenter.getInstanceSlowLogByAppId(appId, startDate, endDate);
          model.addAttribute("appInstanceSlowLogList", appInstanceSlowLogList);
          
          // 各个实例对应的慢查询日志
          Map<String, List<InstanceSlowLog>> instaceSlowLogMap = new HashMap<String, List<InstanceSlowLog>>();
          Map<String, Long> instanceHostPortIdMap = new HashMap<String, Long>();
          for(InstanceSlowLog instanceSlowLog : appInstanceSlowLogList) {
              String hostPort = instanceSlowLog.getIp() + ":" + instanceSlowLog.getPort();
              instanceHostPortIdMap.put(hostPort, instanceSlowLog.getInstanceId());
              if(instaceSlowLogMap.containsKey(hostPort)) {
                  instaceSlowLogMap.get(hostPort).add(instanceSlowLog);
              } else {
                  List<InstanceSlowLog> list = new ArrayList<InstanceSlowLog>();
                  list.add(instanceSlowLog);
                  instaceSlowLogMap.put(hostPort, list);
              }
          }
          model.addAttribute("instaceSlowLogMap", instaceSlowLogMap);
          model.addAttribute("instanceHostPortIdMap", instanceHostPortIdMap);
  
          
          return new ModelAndView("app/slowLog");
      }
      
      /**
       * 清理应用数据
       */
      @RequestMapping(value = "/cleanAppData")
      public ModelAndView doCleanAppData(HttpServletRequest request, HttpServletResponse response, Model model, long appId) {
          AppUser appUser = getUserInfo(request);
          logger.warn("{} start to clean appId={} data!", appUser.getName(), appId);
          SuccessEnum successEnum = SuccessEnum.FAIL;
          if (appId > 0) {
              //验证用户对应用的权限 以及数据清理的结果
              if (checkAppUserProvilege(request, appId) && appDeployCenter.cleanAppData(appId, getUserInfo(request))) {
                  successEnum = SuccessEnum.SUCCESS;
              }
          }
          logger.warn("{} end to clean appId={} data, result is {}", appUser.getName(), appId, successEnum.info());
          write(response, String.valueOf(successEnum.value()));
          return null;
      }
  
  
      /**
       * AppCommandGroup列表组装成json串
       */
      private String assembleGroupJson(List<AppCommandGroup> appCommandGroupList) {
          if (appCommandGroupList == null || appCommandGroupList.isEmpty()) {
              return "[]";
          }
          List<SimpleChartData> list = new ArrayList<SimpleChartData>();
          for (AppCommandGroup appCommandGroup : appCommandGroupList) {
              SimpleChartData chartData = SimpleChartData
                      .getFromAppCommandGroup(appCommandGroup);
              list.add(chartData);
          }
          JSONArray jsonArray = JSONArray.fromObject(list);
          return jsonArray.toString();
      }
  
      /**
       * AppStats列表组装成json串
       */
      private String assembleAppStatsJson(List<AppStats> appStats, String statName) {
          if (appStats == null || appStats.isEmpty()) {
              return "[]";
          }
          List<SimpleChartData> list = new ArrayList<SimpleChartData>();
          for (AppStats stat : appStats) {
              try {
                  SimpleChartData chartData = SimpleChartData.getFromAppStats(stat, statName);
                  list.add(chartData);
              } catch (ParseException e) {
                  logger.info(e.getMessage(), e);
              }
          }
          JSONArray jsonArray = JSONArray.fromObject(list);
          return jsonArray.toString();
      }
      
      private String assembleMutilDateAppCommandJsonMinute(List<AppCommandStats> appCommandStats, Date startDate, Date endDate) {
          if (appCommandStats == null || appCommandStats.isEmpty()) {
              return "[]";
          }
          Map<String, List<HighchartPoint>> map = new HashMap<String, List<HighchartPoint>>();
          Date currentDate = DateUtils.addDays(endDate, -1);
          int diffDays = 0;
          while (currentDate.getTime() >= startDate.getTime()) {
              List<HighchartPoint> list = new ArrayList<HighchartPoint>();
              for (AppCommandStats stat : appCommandStats) {
                  try {
                      HighchartPoint highchartPoint = HighchartPoint.getFromAppCommandStats(stat, currentDate, diffDays);
                      if (highchartPoint == null) {
                          continue;
                      }
                      list.add(highchartPoint);
                  } catch (ParseException e) {
                      logger.info(e.getMessage(), e);
                  }
              }
              String formatDate = DateUtil.formatDate(currentDate, "yyyy-MM-dd");
              map.put(formatDate, list);
              currentDate = DateUtils.addDays(currentDate, -1);
              diffDays++;
          }
          net.sf.json.JSONObject jsonObject = net.sf.json.JSONObject.fromObject(map);
          return jsonObject.toString();
      }
      
      /**
       * 多命令组装
       * @param appStats
       * @param statNameList
       * @param startDate
       * @return
       */
      private String assembleMutiStatAppStatsJsonMinute(List<AppStats> appStats, List<String> statNameList, Date startDate) {
          if (appStats == null || appStats.isEmpty()) {
              return "[]";
          }
          Map<String, List<HighchartPoint>> map = new HashMap<String, List<HighchartPoint>>();
          for(String statName : statNameList) {
              List<HighchartPoint> list = new ArrayList<HighchartPoint>();
              for (AppStats stat : appStats) {
                  try {
                      HighchartPoint highchartPoint = HighchartPoint.getFromAppStats(stat, statName, startDate, 0);
                      if (highchartPoint == null) {
                          continue;
                      }
                      list.add(highchartPoint);
                  } catch (ParseException e) {
                      logger.info(e.getMessage(), e);
                  }
              }
              map.put(statName, list);
          }
          net.sf.json.JSONObject jsonObject = net.sf.json.JSONObject.fromObject(map);
          return jsonObject.toString();
      }
      
      /**
       * 多时间组装
       * @param appStats
       * @param statName
       * @param startDate
       * @param endDate
       * @return
       */
      private String assembleMutilDateAppStatsJsonMinute(List<AppStats> appStats, String statName, Date startDate, Date endDate) {
          if (appStats == null || appStats.isEmpty()) {
              return "[]";
          }
          Map<String, List<HighchartPoint>> map = new HashMap<String, List<HighchartPoint>>();
          Date currentDate = DateUtils.addDays(endDate, -1);
          int diffDays = 0;
          while (currentDate.getTime() >= startDate.getTime()) {
              List<HighchartPoint> list = new ArrayList<HighchartPoint>();
              for (AppStats stat : appStats) {
                  try {
                      HighchartPoint highchartPoint = HighchartPoint.getFromAppStats(stat, statName, currentDate, diffDays);
                      if (highchartPoint == null) {
                          continue;
                      }
                      list.add(highchartPoint);
                  } catch (ParseException e) {
                      logger.info(e.getMessage(), e);
                  }
              }
              String formatDate = DateUtil.formatDate(currentDate, "yyyy-MM-dd");
              map.put(formatDate, list);
              currentDate = DateUtils.addDays(currentDate, -1);
              diffDays++;
          }
          net.sf.json.JSONObject jsonObject = net.sf.json.JSONObject.fromObject(map);
          return jsonObject.toString();
      }
  
      /**
       * AppCommandStats列表组装成json串
       */
      private String assembleJson(List<AppCommandStats> appCommandStatsList) {
          return assembleJson(appCommandStatsList, null);
      }
  
      private String assembleJson(List<AppCommandStats> appCommandStatsList, Integer addDay) {
          if (appCommandStatsList == null || appCommandStatsList.isEmpty()) {
              return "[]";
          }
          List<SimpleChartData> list = new ArrayList<SimpleChartData>();
          for (AppCommandStats stat : appCommandStatsList) {
              try {
                  SimpleChartData chartData = SimpleChartData
                          .getFromAppCommandStats(stat, addDay);
                  list.add(chartData);
              } catch (ParseException e) {
                  logger.info(e.getMessage(), e);
              }
          }
          JSONArray jsonArray = JSONArray.fromObject(list);
          return jsonArray.toString();
      }
  }
  