| | |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.dsh.course.dto.JsDto; |
| | | import com.dsh.course.dto.StudentQeryDto; |
| | | import com.dsh.course.entity.SiteLock; |
| | |
| | | import com.dsh.guns.config.UserExt; |
| | | import com.dsh.guns.core.base.controller.BaseController; |
| | | import com.dsh.guns.core.util.ToolUtil; |
| | | import com.dsh.guns.modular.system.audit.ManagementAuditWriter; |
| | | import com.dsh.guns.modular.system.controller.util.MD5; |
| | | import com.dsh.guns.modular.system.model.*; |
| | | import com.dsh.guns.modular.system.model.dto.*; |
| | | import com.dsh.guns.modular.system.service.*; |
| | | import com.dsh.guns.modular.system.util.*; |
| | | import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream; |
| | | import org.apache.poi.hssf.usermodel.HSSFWorkbook; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.Row; |
| | | import org.apache.poi.ss.usermodel.Sheet; |
| | | import org.json.JSONArray; |
| | | import org.json.JSONObject; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.imageio.ImageIO; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.IOException; |
| | | import java.io.OutputStream; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.math.BigDecimal; |
| | | import java.text.ParseException; |
| | | import java.text.SimpleDateFormat; |
| | |
| | | import java.time.LocalTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Controller |
| | |
| | | @Autowired |
| | | private AppUserClient appUserClient; |
| | | |
| | | @Resource |
| | | private ManagementAuditWriter auditWriter; |
| | | |
| | | @RequestMapping("/tolist") |
| | | public String tolist(Model model) { |
| | | List<TCity> list = cityService.list(new LambdaQueryWrapper<TCity>().eq(TCity::getParentId, 0)); |
| | |
| | | @RequestMapping("/selectJoin") |
| | | @ResponseBody |
| | | public ResultUtil join(@RequestBody MethodDto method) { |
| | | if (method == null || method.getId() == null) { |
| | | return ResultUtil.error("门票记录不能为空"); |
| | | } |
| | | SiteBooking before = iSiteBookingService.getById(method.getId()); |
| | | if (before == null) { |
| | | return ResultUtil.error("门票记录不存在"); |
| | | } |
| | | Integer previousStatus = before.getStatus(); |
| | | if (!Objects.equals(previousStatus, 1) && !Objects.equals(previousStatus, 4)) { |
| | | return ResultUtil.error("当前门票状态不能确认到店"); |
| | | } |
| | | |
| | | System.out.println("============" + method); |
| | | // 待核销正常确认到店;已过期但运营确认用户实际入场时,直接人工完成。 |
| | | Integer targetStatus = Objects.equals(previousStatus, 4) ? 3 : 2; |
| | | SiteBooking siteBooking = new SiteBooking(); |
| | | siteBooking.setStatus(2); |
| | | siteBooking.setStatus(targetStatus); |
| | | siteBooking.setId(method.getId()); |
| | | siteBooking.setGoType(method.getMethod()); |
| | | iSiteBookingService.updateById(siteBooking); |
| | | try { |
| | | // 以修改前状态作为条件,避免并发操作覆盖其他状态变更。 |
| | | boolean updated = iSiteBookingService.update(siteBooking, |
| | | new LambdaUpdateWrapper<SiteBooking>() |
| | | .eq(SiteBooking::getId, before.getId()) |
| | | .eq(SiteBooking::getStatus, previousStatus)); |
| | | if (!updated) { |
| | | appendTicketStatusAudit(before, targetStatus, method.getMethod(), "FAIL", "门票状态更新失败"); |
| | | return ResultUtil.error("确认到店失败"); |
| | | } |
| | | appendTicketStatusAudit(before, targetStatus, method.getMethod(), "SUCCESS", null); |
| | | } catch (RuntimeException ex) { |
| | | appendTicketStatusAudit(before, targetStatus, method.getMethod(), "FAIL", ex.getMessage()); |
| | | throw ex; |
| | | } |
| | | |
| | | return new ResultUtil<>(0, 0, "到店成功", null, null); |
| | | } |
| | | |
| | | /** 记录运营管理员人工确认到店导致的门票状态变更。 */ |
| | | private void appendTicketStatusAudit(SiteBooking before, Integer targetStatus, Integer goType, |
| | | String result, String errorMessage) { |
| | | try { |
| | | Map<String, Object> beforeData = new HashMap<>(); |
| | | beforeData.put("id", before.getId()); |
| | | beforeData.put("orderNo", before.getOrderNo()); |
| | | beforeData.put("status", before.getStatus()); |
| | | beforeData.put("statusName", ticketStatusName(before.getStatus())); |
| | | |
| | | Map<String, Object> afterData = new HashMap<>(beforeData); |
| | | afterData.put("status", targetStatus); |
| | | afterData.put("statusName", ticketStatusName(targetStatus)); |
| | | afterData.put("goType", goType); |
| | | |
| | | Map<String, Object> audit = new HashMap<>(); |
| | | audit.put("traceId", UUID.randomUUID().toString().replace("-", "")); |
| | | audit.put("sourceService", "mb-cloud-management"); |
| | | audit.put("sourceType", "HTTP"); |
| | | audit.put("operationType", "STATUS_CHANGE"); |
| | | audit.put("riskLevel", "CRITICAL"); |
| | | audit.put("operationName", "运营管理员人工修改门票状态"); |
| | | audit.put("requestMethod", "POST"); |
| | | audit.put("requestUri", "/ball/selectJoin"); |
| | | audit.put("requestParams", "id=" + before.getId() + ", goType=" + goType + ", targetStatus=" + targetStatus); |
| | | audit.put("beforeData", com.alibaba.fastjson.JSONObject.toJSONString(beforeData)); |
| | | audit.put("afterData", com.alibaba.fastjson.JSONObject.toJSONString(afterData)); |
| | | audit.put("userId", UserExt.getUser() == null ? null : UserExt.getUser().getId()); |
| | | audit.put("userAccount", UserExt.getUser() == null ? null : UserExt.getUser().getAccount()); |
| | | audit.put("result", result); |
| | | audit.put("errorMessage", errorMessage); |
| | | // 审计表的 createdAt 即本次管理员操作时间。 |
| | | audit.put("createdAt", new Date()); |
| | | auditWriter.append(audit); |
| | | } catch (Exception ignored) { |
| | | // 审计失败不能阻断实际到店确认,网关日志可作为兜底。 |
| | | } |
| | | } |
| | | |
| | | private String ticketStatusName(Integer status) { |
| | | if (status == null) return "未知"; |
| | | switch (status) { |
| | | case 0: return "待支付"; |
| | | case 1: return "待核销"; |
| | | case 2: return "已到店"; |
| | | case 3: return "已完成"; |
| | | case 4: return "已过期"; |
| | | case 5: return "已取消"; |
| | | default: return "未知"; |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | User user = UserExt.getUser(); |
| | | ballQueryDto.setType(user.getObjectType()); |
| | | ballQueryDto.setId(user.getObjectId()); |
| | | return ballClient.list(ballQueryDto); |
| | | List<Game> games = ballClient.list(ballQueryDto); |
| | | games.sort(Comparator.comparing(Game::getId, Comparator.nullsLast(Comparator.reverseOrder()))); |
| | | return games; |
| | | } |
| | | |
| | | @ResponseBody |
| | |
| | | } |
| | | System.out.println("========strings1=====" + strings1); |
| | | |
| | | //时间段 |
| | | List<String> strings = new ArrayList<>(); |
| | | // List<SiteBooking> siteBookings = siteClient.listBooks(book.getSiteId()); |
| | | List<SiteBooking> siteBookings = iSiteBookingService.list(new QueryWrapper<SiteBooking>().eq("siteId", book.getSiteId()).like("times", split1[0].substring(0, 10))); |
| | | System.out.println("=======siteBookings======" + siteBookings); |
| | | for (SiteBooking siteBooking : siteBookings) { |
| | | String[] split = siteBooking.getTimes().split(";"); |
| | | for (String s : split) { |
| | | |
| | | String convertedValue = s.substring(11); |
| | | strings.add(convertedValue); |
| | | } |
| | | |
| | | } |
| | | System.out.println("======strings=======" + strings); |
| | | ////时间段 |
| | | // List<String> strings = new ArrayList<>(); |
| | | //// List<SiteBooking> siteBookings = siteClient.listBooks(book.getSiteId()); |
| | | // List<SiteBooking> siteBookings = iSiteBookingService.list(new QueryWrapper<SiteBooking>().eq("siteId", book.getSiteId()).like("times", split1[0].substring(0, 10))); |
| | | // System.out.println("=======siteBookings======" + siteBookings); |
| | | // for (SiteBooking siteBooking : siteBookings) { |
| | | // String[] split = siteBooking.getTimes().split(";"); |
| | | // for (String s : split) { |
| | | // |
| | | // String convertedValue = s.substring(11); |
| | | // strings.add(convertedValue); |
| | | // } |
| | | // |
| | | // } |
| | | // System.out.println("======strings=======" + strings); |
| | | |
| | | |
| | | List<OrderDto> timeRanges = new ArrayList<>(); |
| | | |
| | | TSite site = siteService.getById(book.getSiteId()); |
| | | System.out.println("================" + site); |
| | | String appointmentStartTime = site.getAppointmentStartTime(); |
| | | String appointmentEndTime = site.getAppointmentEndTime(); |
| | | //生成用于比较日期是否超过的当前日期 |
| | | LocalDate currentDate = LocalDate.now(); |
| | | LocalDate parsedDate = LocalDate.parse(split1[0].substring(0, 10), DateTimeFormatter.ISO_DATE); |
| | | boolean isAfterCurrentDate = parsedDate.isBefore(currentDate); |
| | | System.out.println("是否超过当前日期" + isAfterCurrentDate); |
| | | String currentTime = appointmentStartTime; |
| | | while (currentTime.compareTo(appointmentEndTime) < 0) { |
| | | String nextTime = getNextTime(currentTime); |
| | | |
| | | |
| | | String timeRange = currentTime + "-" + nextTime; |
| | | OrderDto orderDto = new OrderDto(); |
| | | orderDto.setTime(timeRange); |
| | | |
| | | LocalTime currentTime1 = LocalTime.now(); |
| | | LocalTime targetTime = LocalTime.parse(currentTime); |
| | | |
| | | boolean hasExceeded = currentTime1.isAfter(targetTime); |
| | | //是否超过当前时间 |
| | | if (hasExceeded) { |
| | | orderDto.setState(0); |
| | | } |
| | | //是否已经预定 |
| | | if (strings.contains(timeRange)) { |
| | | orderDto.setState(0); |
| | | } |
| | | //日期是否超过 |
| | | if (isAfterCurrentDate) { |
| | | orderDto.setState(0); |
| | | } |
| | | //是否是当前场地当前日期预定的 |
| | | if (strings1.contains(timeRange)) { |
| | | orderDto.setState(1); |
| | | } |
| | | |
| | | |
| | | timeRanges.add(orderDto); |
| | | currentTime = nextTime; |
| | | } |
| | | // String appointmentStartTime = site.getAppointmentStartTime(); |
| | | // String appointmentEndTime = site.getAppointmentEndTime(); |
| | | // //生成用于比较日期是否超过的当前日期 |
| | | // LocalDate currentDate = LocalDate.now(); |
| | | // LocalDate parsedDate = LocalDate.parse(split1[0].substring(0, 10), DateTimeFormatter.ISO_DATE); |
| | | // boolean isAfterCurrentDate = parsedDate.isBefore(currentDate); |
| | | // System.out.println("是否超过当前日期" + isAfterCurrentDate); |
| | | // String currentTime = appointmentStartTime; |
| | | // while (currentTime.compareTo(appointmentEndTime) < 0) { |
| | | // String nextTime = getNextTime(currentTime); |
| | | // |
| | | // |
| | | // String timeRange = currentTime + "-" + nextTime; |
| | | // OrderDto orderDto = new OrderDto(); |
| | | // orderDto.setTime(timeRange); |
| | | // |
| | | // LocalTime currentTime1 = LocalTime.now(); |
| | | // LocalTime targetTime = LocalTime.parse(currentTime); |
| | | // |
| | | // boolean hasExceeded = currentTime1.isAfter(targetTime); |
| | | // //是否超过当前时间 |
| | | // if (hasExceeded) { |
| | | // orderDto.setState(0); |
| | | // } |
| | | // //是否已经预定 |
| | | // if (strings.contains(timeRange)) { |
| | | // orderDto.setState(0); |
| | | // } |
| | | // //日期是否超过 |
| | | // if (isAfterCurrentDate) { |
| | | // orderDto.setState(0); |
| | | // } |
| | | // //是否是当前场地当前日期预定的 |
| | | // if (strings1.contains(timeRange)) { |
| | | // orderDto.setState(1); |
| | | // } |
| | | // timeRanges.add(orderDto); |
| | | // currentTime = nextTime; |
| | | // } |
| | | System.out.println("-------------" + timeRanges); |
| | | model.addAttribute("timeRanges", timeRanges); |
| | | System.out.println("======timeRanges========" + timeRanges); |
| | | Integer objectType = UserExt.getUser().getObjectType(); |
| | | model.addAttribute("province", book.getProvince()); |
| | | String yuyueTime = ""; |
| | | if (strings1.size()>1){ |
| | | String start = strings1.get(0).split("-")[0]; |
| | | String end = strings1.get(strings1.size()-1).split("-")[1]; |
| | | yuyueTime = start +"-"+end; |
| | | }else{ |
| | | yuyueTime = strings1.get(0); |
| | | } |
| | | model.addAttribute("yuyueTime", yuyueTime); |
| | | model.addAttribute("targetAudience", book.getTargetAudience()); |
| | | model.addAttribute("city", book.getCity()); |
| | | model.addAttribute("store", storeService.getById(book.getStoreId()).getName()); |
| | | model.addAttribute("siteName", siteService.getById(book.getSiteId()).getName()); |
| | |
| | | @RequestMapping("/yuyuelist") |
| | | @ResponseBody |
| | | public List<Map<String, Object>> yuyuelist(BookingQuery bookingQuery) { |
| | | return queryBookingOrders(bookingQuery); |
| | | } |
| | | |
| | | /** |
| | | * 导出当前筛选条件下的全部预约记录,不受列表分页影响。 |
| | | */ |
| | | @RequestMapping("/yuyueexport") |
| | | public void exportYuyue(BookingQuery bookingQuery, HttpServletResponse response) throws IOException { |
| | | List<Map<String, Object>> orders = queryBookingOrders(bookingQuery); |
| | | String[] headers = {"序号", "所在省", "所在市", "预约门店", "入场方式", "预约场地", "姓名", "联系电话", |
| | | "预约时间段", "支付流水号", "支付方式", "金额", "状态"}; |
| | | |
| | | // 线上运行环境使用的是旧版 POI 核心包,使用 HSSF 保持依赖兼容; |
| | | // 超过单 Sheet 上限的数据拆分到多个工作表,确保导出全部命中记录。 |
| | | HSSFWorkbook workbook = new HSSFWorkbook(); |
| | | try { |
| | | final int maxDataRowsPerSheet = 60000; |
| | | int sheetCount = Math.max(1, (orders.size() + maxDataRowsPerSheet - 1) / maxDataRowsPerSheet); |
| | | for (int sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++) { |
| | | Sheet sheet = workbook.createSheet(sheetCount == 1 ? "预约管理" : "预约管理" + (sheetIndex + 1)); |
| | | Row header = sheet.createRow(0); |
| | | for (int i = 0; i < headers.length; i++) { |
| | | header.createCell(i).setCellValue(headers[i]); |
| | | sheet.setColumnWidth(i, i == 8 ? 28 * 256 : 18 * 256); |
| | | } |
| | | int start = sheetIndex * maxDataRowsPerSheet; |
| | | int end = Math.min(start + maxDataRowsPerSheet, orders.size()); |
| | | for (int rowIndex = start; rowIndex < end; rowIndex++) { |
| | | Map<String, Object> order = orders.get(rowIndex); |
| | | Row row = sheet.createRow(rowIndex - start + 1); |
| | | String[] values = { |
| | | valueOf(order.get("id")), valueOf(order.get("province")), valueOf(order.get("city")), |
| | | valueOf(order.get("name")), targetAudienceName(order.get("targetAudience")), |
| | | valueOf(order.get("siteName")), valueOf(order.get("booker")), valueOf(order.get("phone")), |
| | | valueOf(order.get("times")), valueOf(order.get("payOrderNo")), valueOf(order.get("payType")), |
| | | moneyText(order.get("payType"), order.get("payMoney")), ticketStatusName(toInteger(order.get("status"))) |
| | | }; |
| | | for (int columnIndex = 0; columnIndex < values.length; columnIndex++) { |
| | | Cell cell = row.createCell(columnIndex); |
| | | cell.setCellValue(values[columnIndex]); |
| | | } |
| | | } |
| | | } |
| | | |
| | | String fileName = "预约管理_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xls"; |
| | | response.reset(); |
| | | response.setContentType("application/vnd.ms-excel"); |
| | | response.setCharacterEncoding(StandardCharsets.UTF_8.name()); |
| | | response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" |
| | | + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20")); |
| | | try (OutputStream output = response.getOutputStream()) { |
| | | workbook.write(output); |
| | | output.flush(); |
| | | } |
| | | } finally { |
| | | // HSSFWorkbook 由旧版 POI 提供,不调用新版 Workbook 的 close API。 |
| | | } |
| | | } |
| | | |
| | | /** 列表和导出共用同一查询与数据展示转换,保证权限范围和筛选条件一致。 */ |
| | | private List<Map<String, Object>> queryBookingOrders(BookingQuery bookingQuery) { |
| | | Integer objectType = UserExt.getUser().getObjectType(); |
| | | |
| | | if (objectType == 2) { |
| | | List<Integer> operatorId = storeService.list(new QueryWrapper<TStore>() |
| | | .eq("operatorId", UserExt.getUser().getObjectId())) |
| | | .stream().map(TStore::getId).collect(Collectors.toList()); |
| | | if (operatorId.isEmpty()){ |
| | | operatorId.add(0); |
| | | } |
| | | bookingQuery.setStoreIds(operatorId); |
| | | } |
| | | if (objectType == 3) { |
| | |
| | | // } |
| | | } |
| | | return orders; |
| | | } |
| | | |
| | | private String targetAudienceName(Object targetAudience) { |
| | | Integer value = toInteger(targetAudience); |
| | | if (Objects.equals(value, 1)) return "订场"; |
| | | if (Objects.equals(value, 2)) return "散客"; |
| | | return ""; |
| | | } |
| | | |
| | | private String moneyText(Object payType, Object payMoney) { |
| | | if (payMoney == null) return ""; |
| | | return valueOf(payMoney) + ("玩湃币".equals(valueOf(payType)) ? "币" : "元"); |
| | | } |
| | | |
| | | private Integer toInteger(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | try { |
| | | return Integer.valueOf(value.toString()); |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String valueOf(Object value) { |
| | | return value == null ? "" : String.valueOf(value); |
| | | } |
| | | |
| | | |
| | |
| | | siteBooking.setAddType(1); |
| | | |
| | | //存开始和结束时间 |
| | | // times格式示例: 2025-10-14 19:00-19:30;2025-10-14 19:30-20:00;2025-10-14 20:00-20:30;2025-10-14 20:30-21:00 |
| | | // targetAudience字段会由Spring MVC自动绑定(1=订场,2=散客) |
| | | String times = siteBooking.getTimes(); |
| | | if (ToolUtil.isEmpty(times)) { |
| | | return ResultUtil.error("请选择预约时间段"); |
| | | } |
| | | String[] dates = times.split(";"); |
| | | if (dates.length == 0) { |
| | | return ResultUtil.error("请选择预约时间段"); |
| | | } |
| | | |
| | | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm"); |
| | | format.setLenient(false); |
| | | try { |
| | | String start = dates[0]; |
| | | String startDateStr = start.substring(0, start.lastIndexOf("-")); |
| | | Date startDate = format.parse(startDateStr); |
| | | Pattern timeSlotPattern = Pattern.compile("^(\\d{4}-\\d{2}-\\d{2})\\s+(\\d{2}:\\d{2})-(\\d{2}:\\d{2})$"); |
| | | // dates[0] 格式: 2025-10-14 19:00-19:30 |
| | | Matcher startMatcher = timeSlotPattern.matcher(dates[0].trim()); |
| | | // dates[dates.length - 1] 格式: 2025-10-14 20:30-21:00 |
| | | Matcher endMatcher = timeSlotPattern.matcher(dates[dates.length - 1].trim()); |
| | | if (!startMatcher.matches() || !endMatcher.matches()) { |
| | | return ResultUtil.error("预约时间段格式错误,请重新选择"); |
| | | } |
| | | |
| | | String end = dates[dates.length - 1]; |
| | | String[] str = end.split(" "); |
| | | String endDateStr = str[0] + " " + str[1].substring(str[1].indexOf("-") + 1); |
| | | Date endDate = format.parse(endDateStr); |
| | | Date startDate = format.parse(startMatcher.group(1) + " " + startMatcher.group(2)); |
| | | Date endDate = format.parse(endMatcher.group(1) + " " + endMatcher.group(3)); |
| | | if (!endDate.after(startDate)) { |
| | | return ResultUtil.error("预约结束时间必须晚于开始时间"); |
| | | } |
| | | |
| | | siteBooking.setStartTime(startDate); |
| | | siteBooking.setEndTime(endDate); |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | return ResultUtil.error("预约时间段格式错误,请重新选择"); |
| | | } |
| | | siteBooking.setPayType(2); |
| | | //存多少钱 |
| | | String money = siteBooking.getMoney(); |
| | | if (ToolUtil.isEmpty(money)) { |
| | | return ResultUtil.error("请输入预约金额"); |
| | | } |
| | | String[] moneys = money.split(","); |
| | | siteBooking.setPayMoney(Double.valueOf(moneys[0])); |
| | | if (moneys.length == 0 || ToolUtil.isEmpty(moneys[0])) { |
| | | return ResultUtil.error("预约金额格式错误"); |
| | | } |
| | | try { |
| | | siteBooking.setPayMoney(Double.valueOf(moneys[0])); |
| | | } catch (NumberFormatException e) { |
| | | return ResultUtil.error("预约金额格式错误"); |
| | | } |
| | | siteBooking.setMoney(money); |
| | | // 查询当前预约人是否是会员 |
| | | String phone = siteBooking.getPhone(); |
| | | String booker = siteBooking.getBooker(); |
| | |
| | | siteBooking.setCityCode(byId.getCityCode()); |
| | | } |
| | | |
| | | if (siteBooking.getIsHalf()==2){ |
| | | siteBooking.setHalfName(null); |
| | | } |
| | | siteClient.addSiteBooking(siteBooking); |
| | | System.out.println("=================" + siteBooking); |
| | | return new ResultUtil<>(null, 200, null, null, null); |