ie8下Date.parse方法失效

本文介绍了在Internet Explorer 8中使用JavaScript的Date.parse方法解析特定格式日期字符串时遇到的问题及解决方案。由于IE8不识别短划线(-),文章提供了一种通过正则表达式替换字符来解决该问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

事件:ie8中使用Date.parse将yyyy-MM-dd hh:mm:ss格式的时间转化为毫秒数失败

原因:ie8不识别-

解决方法:需要利用正则表达式(yyyy-MM-dd hh:mm:ss).replace(/-/g, '/')将-转为/

 

JavaScript中Date.parse 函数用法

http://younglab.blog.51cto.com/416652/263232← ←写的比较详细

 

package org.example; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; /** * 飞书公司圈帖子数据抓取并同步至多维表格 */ @RestController @RequestMapping("/feishu/events") public class MomentsBitableIntegration { private static final Logger logger = LoggerFactory.getLogger(MomentsBitableIntegration.class); private static final String APP_ID = "cli_a77e623b63fbd00d"; private static final String APP_SECRET = "p1y9z84vBOxSClmqn4y0CcVJPrdKeF3Y"; private static final String ENCRYPT_KEY = "eGXBxvckaDb7NdN8gC4ZW4YQishxILFi"; // 飞书事件加密密钥 private static final String TENANT_ACCESS_TOKEN_URL = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"; private static final String BITABLE_APP_TOKEN = "NQyxb7bxma1BNDszeFqcHLrbnje"; private static final String BITABLE_TABLE_ID = "tblycyMgokQQDoAd"; private static final String MOMENTS_POST_API = "https://open.feishu.cn/open-apis/moments/v1/posts/"; private static final String USER_INFO_API = "https://open.feishu.cn/open-apis/contact/v3/users/"; // 日期时间格式化器,用于解析多种格式的日期 private static final DateTimeFormatter[] DATE_TIME_FORMATTERS = { DateTimeFormatter.ISO_OFFSET_DATE_TIME, DateTimeFormatter.ISO_LOCAL_DATE_TIME, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") }; private final OkHttpClient httpClient = new OkHttpClient(); private final Gson gson = new Gson(); private final JsonParser jsonParser = new JsonParser(); private String tenantAccessToken; private long lastTokenRefreshTime; // 记录上次令牌刷新时间 private ScheduledExecutorService scheduler; private Map<String, PostData> postDataMap = new ConcurrentHashMap<>(); private final Map<String, String> userCache = new ConcurrentHashMap<>(); // 用户信息缓存 private final EventReceiver eventReceiver; // 帖子数据结构 static class PostData { String postId; String author; // 作者姓名 String authorId; // 作者ID(open_id等) List<String> mentionedUsers = new ArrayList<>(); int likeCount; int dislikeCount; double popularityScore; String content; long publishTimeUnix = 0; // 存储毫秒级Unix时间戳 public PostData(String postId) { this.postId = postId; } } public MomentsBitableIntegration() { this.eventReceiver = new EventReceiver(this); init(); scheduleWeeklyReport(); } // 初始化飞书认证 public void init() { try { refreshAccessToken(); scheduler = Executors.newScheduledThreadPool(2); // 添加定时刷新token任务(每90分钟刷新一次) scheduler.scheduleAtFixedRate(() -> { try { refreshAccessToken(); } catch (Exception e) { logger.error("刷新访问令牌失败", e); } }, 0, 90, TimeUnit.MINUTES); logger.info("飞书集成服务初始化完成"); } catch (Exception e) { logger.error("初始化失败", e); System.exit(1); } } // 刷新访问令牌 private void refreshAccessToken() throws IOException { MediaType JSON = MediaType.get("application/json; charset=utf-8"); String json = "{\"app_id\":\"" + APP_ID + "\",\"app_secret\":\"" + APP_SECRET + "\"}"; okhttp3.RequestBody body = okhttp3.RequestBody.create(json, JSON); Request request = new Request.Builder() .url(TENANT_ACCESS_TOKEN_URL) .post(body) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { logger.error("刷新令牌请求失败: HTTP {}", response.code()); throw new IOException("刷新令牌失败: HTTP " + response.code()); } String responseData = response.body().string(); Type type = new TypeToken<Map<String, Object>>(){}.getType(); Map<String, Object> result = gson.fromJson(responseData, type); if (result.containsKey("tenant_access_token")) { tenantAccessToken = (String) result.get("tenant_access_token"); lastTokenRefreshTime = System.currentTimeMillis(); logger.info("访问令牌刷新成功"); } else { logger.error("获取访问令牌失败: {}", responseData); throw new IOException("获取访问令牌失败: " + result.get("msg")); } } } // 检查令牌是否有效 private boolean isTokenValid() { // 令牌有效期为2小时,提前10分钟刷新 return System.currentTimeMillis() - lastTokenRefreshTime < 110 * 60 * 1000; } // 处理飞书事件推送 @PostMapping("/callback") public Map<String, Object> handleFeishuEvent(HttpServletRequest request) { String eventJson = null; try { // 读取请求体 StringBuilder requestBody = new StringBuilder(); try (BufferedReader reader = request.getReader()) { String line; while ((line = reader.readLine()) != null) { requestBody.append(line); } } eventJson = requestBody.toString(); logger.info("收到飞书事件: {}", eventJson); // 用于传递给EventReceiver的事件数据 String eventJsonForReceiver = null; Map<String, Object> eventData; // 判断是否为加密数据 if (eventJson.contains("\"encrypt\"")) { Map<String, Object> encryptedMap = gson.fromJson( eventJson, new TypeToken<Map<String, Object>>() {}.getType() ); String encryptedData = (String) encryptedMap.get("encrypt"); if (encryptedData == null || encryptedData.isEmpty()) { logger.warn("加密数据为空: {}", eventJson); return createErrorResponse(400, "Missing encrypt data"); } // 解密获取原始事件数据 String decryptedJson = FeishuEncryptUtils.decrypt(ENCRYPT_KEY, encryptedData); logger.info("解密后事件数据: {}", decryptedJson); eventJsonForReceiver = decryptedJson; eventData = gson.fromJson(decryptedJson, new TypeToken<Map<String, Object>>() {}.getType()); } else { eventJsonForReceiver = eventJson; eventData = gson.fromJson(eventJson, new TypeToken<Map<String, Object>>() {}.getType()); } if (eventData == null) { logger.warn("事件数据为空: {}", eventJson); return createErrorResponse(400, "Invalid event data"); } // 1. 优先处理URL验证事件 if (isUrlVerificationEvent(eventData)) { return handleUrlVerification(eventData); } // 2. 获取header Map<String, Object> header = getEventHeader(eventData); if (header == null) { logger.warn("事件header为空: {}", eventJson); return createErrorResponse(400, "Missing event header"); } // 3. 获取事件类型 String eventType = (String) header.get("event_type"); if (eventType == null) { logger.warn("事件类型为空: {}", eventJson); return createErrorResponse(400, "Missing event_type in event data"); } // 4. 验证签名 if (!verifySignature(request, eventJson)) { logger.warn("事件签名验证失败"); return createErrorResponse(401, "Unauthorized"); } // 传递事件数据 eventReceiver.handleEvent(eventJsonForReceiver); return createSuccessResponse(); } catch (Exception e) { logger.error("处理飞书事件失败: {}", eventJson, e); return createErrorResponse(500, "Internal Server Error"); } } // ===== 辅助方法 ===== private boolean isUrlVerificationEvent(Map<String, Object> eventData) { return "url_verification".equals(eventData.get("type")); } private Map<String, Object> getEventHeader(Map<String, Object> eventData) { Object headerObj = eventData.get("header"); if (headerObj instanceof Map) { @SuppressWarnings("unchecked") Map<String, Object> header = (Map<String, Object>) headerObj; return header; } return null; } private Map<String, Object> handleUrlVerification(Map<String, Object> eventData) { String challenge = (String) eventData.get("challenge"); if (challenge == null) { logger.warn("URL验证请求缺少challenge参数"); return createErrorResponse(400, "Missing challenge parameter"); } logger.info("完成URL验证,challenge: {}", challenge); return Collections.singletonMap("challenge", challenge); } // 验证事件签名 private boolean verifySignature(HttpServletRequest request, String eventJson) { if (ENCRYPT_KEY == null || ENCRYPT_KEY.isEmpty()) { return true; } String timestamp = request.getHeader("X-Lark-Request-Timestamp"); String nonce = request.getHeader("X-Lark-Request-Nonce"); String signature = request.getHeader("X-Lark-Signature"); if (timestamp == null || nonce == null || signature == null) { logger.warn("缺少签名参数:timestamp={}, nonce={}, signature={}", timestamp, nonce, signature); return false; } String signStr = timestamp + nonce + ENCRYPT_KEY + eventJson; try { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] hashBytes = md.digest(signStr.getBytes(StandardCharsets.UTF_8)); String calculatedSignature = bytesToHex(hashBytes); return calculatedSignature.equals(signature); } catch (NoSuchAlgorithmException e) { logger.error("签名计算失败", e); return false; } } // 字节数组转十六进制字符串 private String bytesToHex(byte[] bytes) { StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { String hex = String.format("%02x", b); hexString.append(hex); } return hexString.toString(); } // 处理表情互动创建事件 public void handleReactionCreatedEvent(Map<String, Object> eventData) { try { Map<String, Object> event = (Map<String, Object>) eventData.get("event"); if (event == null) { logger.warn("无效的表情互动事件: {}", eventData); return; } String postId = (String) event.get("entity_id"); String reactionType = (String) event.get("type"); if (postId == null || reactionType == null) { logger.warn("表情互动事件缺少必要字段: postId={}, reactionType={}", postId, reactionType); return; } PostData postData = postDataMap.computeIfAbsent(postId, PostData::new); // 核心增强:通过帖子ID获取作者信息 if (postData.author == null || postData.authorId == null) { try { // 第一步:获取帖子详情(包含作者ID) fetchPostDetails(postData); // 第二步:如果获取到了作者ID但还没有姓名,则调用用户接口 if (postData.authorId != null && (postData.author == null || postData.author.equals("未知作者"))) { logger.info("通过表情互动事件获取作者信息: 帖子ID={} → 作者ID={}", postId, postData.authorId); postData.author = getUserName(postData.authorId); logger.info("获取作者姓名成功: {} → {}", postData.authorId, postData.author); } } catch (Exception e) { logger.error("通过表情互动事件获取作者信息失败", e); } } if ("THUMBSUP".equals(reactionType)) { postData.likeCount++; logger.info("帖子 {} 收到新点赞,当前点赞数: {}", postId, postData.likeCount); } updatePopularityScore(postData); } catch (Exception e) { logger.error("处理表情互动事件失败", e); } } // 处理表情互动删除事件 public void handleReactionDeletedEvent(Map<String, Object> eventData) { try { Map<String, Object> event = (Map<String, Object>) eventData.get("event"); if (event == null) { logger.warn("无效的表情互动删除事件: {}", eventData); return; } String postId = (String) event.get("entity_id"); String reactionType = (String) event.get("type"); if (postId == null || reactionType == null) { logger.warn("表情互动删除事件缺少必要字段: postId={}, reactionType={}", postId, reactionType); return; } PostData postData = postDataMap.computeIfAbsent(postId, PostData::new); // 通过帖子ID获取作者信息 if (postData.author == null || postData.authorId == null) { try { fetchPostDetails(postData); if (postData.authorId != null && (postData.author == null || postData.author.equals("未知作者"))) { postData.author = getUserName(postData.authorId); } } catch (Exception e) { logger.error("获取帖子详情失败,但将继续处理事件", e); } } if ("THUMBSUP".equals(reactionType)) { if (postData.likeCount > 0) { postData.likeCount--; logger.info("帖子 {} 取消点赞,当前点赞数: {}", postId, postData.likeCount); } else { logger.warn("帖子 {} 点赞数为0,但收到取消点赞事件", postId); } } updatePopularityScore(postData); } catch (Exception e) { logger.error("处理表情互动删除事件失败", e); } } // 处理点踩创建事件 public void handleDislikeCreatedEvent(Map<String, Object> eventData) { try { Map<String, Object> event = (Map<String, Object>) eventData.get("event"); if (event == null) { logger.warn("无效的点踩事件: {}", eventData); return; } String postId = (String) event.get("entity_id"); if (postId == null) { logger.warn("点踩事件缺少必要字段: {}", eventData); return; } PostData postData = postDataMap.computeIfAbsent(postId, PostData::new); // 通过帖子ID获取作者信息 if (postData.author == null || postData.authorId == null) { try { fetchPostDetails(postData); if (postData.authorId != null && (postData.author == null || postData.author.equals("未知作者"))) { postData.author = getUserName(postData.authorId); } } catch (Exception e) { logger.error("获取帖子详情失败,但将继续处理事件", e); } } postData.dislikeCount++; logger.info("帖子 {} 收到新点踩,当前点踩数: {}", postId, postData.dislikeCount); updatePopularityScore(postData); } catch (Exception e) { logger.error("处理点踩事件失败", e); } } // 处理点踩删除事件 public void handleDislikeDeletedEvent(Map<String, Object> eventData) { try { Map<String, Object> event = (Map<String, Object>) eventData.get("event"); if (event == null) { logger.warn("无效的点踩删除事件: {}", eventData); return; } String postId = (String) event.get("entity_id"); if (postId == null) { logger.warn("点踩删除事件缺少必要字段: {}", eventData); return; } PostData postData = postDataMap.computeIfAbsent(postId, PostData::new); // 通过帖子ID获取作者信息 if (postData.author == null || postData.authorId == null) { try { fetchPostDetails(postData); if (postData.authorId != null && (postData.author == null || postData.author.equals("未知作者"))) { postData.author = getUserName(postData.authorId); } } catch (Exception e) { logger.error("获取帖子详情失败,但将继续处理事件", e); } } if (postData.dislikeCount > 0) { postData.dislikeCount--; logger.info("帖子 {} 取消点踩,当前点踩数: {}", postId, postData.dislikeCount); } else { logger.warn("帖子 {} 点踩数为0,但收到取消点踩事件", postId); } updatePopularityScore(postData); } catch (Exception e) { logger.error("处理点踩删除事件失败", e); } } public void handlePostCreatedEvent(Map<String, Object> eventData) { try { Map<String, Object> event = (Map<String, Object>) eventData.get("event"); if (event == null) { logger.warn("无效的帖子创建事件: {}", eventData); return; } String postId = (String) event.get("id"); if (postId == null) { logger.warn("帖子创建事件缺少必要字段: {}", eventData); return; } PostData postData = postDataMap.computeIfAbsent(postId, PostData::new); try { fetchPostDetails(postData); } catch (Exception e) { logger.error("获取帖子详情失败", e); } Map<String, Object> userIdentity = (Map<String, Object>) event.get("user_id"); String eventUserId = null; if (userIdentity != null) { eventUserId = (String) userIdentity.get("open_id"); if (eventUserId == null) { eventUserId = (String) userIdentity.get("union_id"); } } if (postData.author == null && eventUserId != null) { try { postData.author = getUserName(eventUserId); } catch (Exception e) { logger.error("获取用户姓名失败", e); postData.author = eventUserId; } } logger.info("新帖子创建: {}, 作者ID: {}, 发布时间: {}", postId, eventUserId, postData.publishTimeUnix); logger.info("作者姓名: {}", postData.author); } catch (Exception e) { logger.error("处理帖子创建事件失败", e); } } // 判断用户ID类型 private String determineUserIdType(String userId) { if (userId.startsWith("ou_")) return "open_id"; if (userId.startsWith("u_")) return "user_id"; if (userId.startsWith("on_")) return "union_id"; return null; } // 获取用户姓名(带缓存和重试机制) private String getUserName(String userId) throws IOException { // 检查缓存 if (userCache.containsKey(userId)) { String cachedName = userCache.get(userId); logger.info("用户信息命中缓存: {} → {}", userId, cachedName); // 调试日志:检查缓存中的名称是否是中文 if (!isValidChineseName(cachedName)) { logger.warn("缓存中的名称不是中文: {} → {}", userId, cachedName); } return cachedName; } if (userId == null || userId.isEmpty()) { logger.warn("用户ID为空"); return "无效ID"; } String userType = determineUserIdType(userId); if (userType == null) { logger.warn("不支持的用户ID类型: {}", userId); return "未知用户"; } int retries = 3; while (retries-- > 0) { try { String url = USER_INFO_API + userId + "?department_id_type=open_department_id&user_id_type=" + userType; logger.debug("调用用户信息API: {}", url); Request request = new Request.Builder() .url(url) .header("Authorization", "Bearer " + tenantAccessToken) .get() .build(); try (Response response = httpClient.newCall(request).execute()) { logger.debug("用户信息接口响应: HTTP {}", response.code()); if (!response.isSuccessful()) { logger.error("用户信息接口错误: HTTP {} - {}", response.code(), response.message()); if (response.code() == 429) { Thread.sleep(1000); continue; } else if (response.code() == 404) { logger.warn("用户不存在: {}", userId); return "已离职用户"; } throw new IOException("获取用户信息失败: HTTP " + response.code()); } String responseData = response.body().string(); logger.debug("用户信息API原始响应: {}", responseData); // 添加原始响应日志 JsonObject result = jsonParser.parse(responseData).getAsJsonObject(); if (result.has("code") && result.get("code").getAsInt() != 0) { logger.error("飞书API返回错误: {}", responseData); continue; } JsonObject data = result.getAsJsonObject("data"); if (data != null && data.has("user")) { JsonObject user = data.getAsJsonObject("user"); // === 详细调试日志开始 === logger.info("完整用户对象: {}", user.toString()); // 记录完整用户对象 String userName = null; String source = "未知来源"; // 1. 优先使用name字段 if (user.has("name")) { JsonElement nameElement = user.get("name"); if (nameElement.isJsonPrimitive()) { userName = nameElement.getAsString().trim(); source = "name字段"; logger.info("从{}获取用户名: {}", source, userName); // 检查是否是有效中文名 if (isValidChineseName(userName)) { logger.debug("name字段是有效中文名"); } else { logger.warn("name字段不是中文名: {}", userName); } } } // 2. 尝试custom_attrs获取中文名 if ((userName == null || !isValidChineseName(userName)) && user.has("custom_attrs")) { JsonObject customAttrs = user.getAsJsonObject("custom_attrs"); logger.debug("检查custom_attrs: {}", customAttrs); for (String key : customAttrs.keySet()) { // 查找包含"中文"或"姓名"的自定义字段 if (key.contains("中文") || key.contains("姓名")) { JsonElement attrElement = customAttrs.get(key); if (attrElement.isJsonPrimitive()) { String candidate = attrElement.getAsString().trim(); logger.debug("找到可能的中文名字段: {} = {}", key, candidate); if (isValidChineseName(candidate)) { userName = candidate; source = "custom_attrs." + key; logger.info("从{}获取中文名: {}", source, userName); break; } } } } } // 3. 尝试en_name字段 if ((userName == null || userName.isEmpty()) && user.has("en_name")) { JsonElement enNameElement = user.get("en_name"); if (enNameElement.isJsonPrimitive()) { userName = enNameElement.getAsString().trim(); source = "en_name字段"; logger.info("从{}获取用户名: {}", source, userName); } } // 4. 回退到user_id if ((userName == null || userName.isEmpty()) && user.has("user_id")) { JsonElement userIdElement = user.get("user_id"); if (userIdElement.isJsonPrimitive()) { userName = userIdElement.getAsString().trim(); source = "user_id字段"; logger.info("从{}获取用户名: {}", source, userName); } } // 5. 最终回退 if (userName == null || userName.isEmpty()) { userName = "未知用户"; source = "默认值"; logger.warn("无法获取有效用户名,使用默认值"); } // 检查最终用户名是否是中文 if (!isValidChineseName(userName)) { logger.warn("最终用户名不是中文: {} → {} (来源: {})", userId, userName, source); logger.warn("完整用户对象: {}", user.toString()); } else { logger.info("获取到中文名: {} → {}", userId, userName); } // === 详细调试日志结束 === // 缓存结果 logger.info("缓存用户信息: {} → {}", userId, userName); userCache.put(userId, userName); logger.info("最终确定的用户名: {} → {}", userId, userName); return userName; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("用户信息请求被中断", e); } catch (Exception e) { logger.error("获取用户信息异常", e); if (retries == 0) { logger.error("最终获取用户信息失败: {}", userId, e); return "未知用户"; } try { Thread.sleep(2000); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } } return "未知用户"; } // 检查是否是有效中文名 private boolean isValidChineseName(String name) { if (name == null || name.isEmpty()) return false; // 简单检查:包含中文字符即为有效 return name.matches(".*[\\u4e00-\\u9fa5]+.*"); } // 获取帖子详情(严格使用user_id字段) private void fetchPostDetails(PostData postData) throws IOException { Request request = new Request.Builder() .url(MOMENTS_POST_API + postData.postId) .header("Authorization", "Bearer " + tenantAccessToken) .get() .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("获取帖子详情失败: HTTP " + response.code()); } String responseData = response.body().string(); logger.debug("帖子详情API响应: {}", responseData); JsonObject result = jsonParser.parse(responseData).getAsJsonObject(); JsonObject data = result.getAsJsonObject("data"); if (data != null) { JsonObject post = data.getAsJsonObject("post"); if (post != null) { // ===== 严格使用user_id字段 ===== String authorId = null; String authorSource = "unknown"; // 只使用user_id字段获取作者ID if (post.has("user_id")) { JsonElement userIdElem = post.get("user_id"); // 处理字符串格式的user_id if (userIdElem.isJsonPrimitive()) { authorId = userIdElem.getAsString(); authorSource = "user_id"; logger.info("从user_id字段获取作者ID: {}", authorId); // 标准化ID格式(确保以ou_开头) if (!authorId.startsWith("ou_") && authorId.length() == 32) { authorId = "ou_" + authorId; logger.info("标准化作者ID格式: {} → {}", authorId, authorId); } } // 处理对象格式的user_id else if (userIdElem.isJsonObject()) { JsonObject userIdObj = userIdElem.getAsJsonObject(); if (userIdObj.has("open_id")) { authorId = userIdObj.get("open_id").getAsString(); authorSource = "user_id.open_id"; logger.info("从user_id.open_id获取作者ID: {}", authorId); } } } // 存储作者ID if (authorId != null) { postData.authorId = authorId; logger.info("获取到帖子作者ID: {} (来源: {})", authorId, authorSource); // 获取作者姓名 if (postData.author == null) { try { postData.author = getUserName(authorId); logger.info("通过作者ID获取姓名: {} → {}", authorId, postData.author); } catch (Exception e) { logger.error("获取作者姓名失败", e); postData.author = "未知作者"; } } } else { logger.warn("无法确定帖子作者ID: {}", postData.postId); } // ===== 其他字段处理 ===== // 提及用户处理 if (post.has("mentions")) { for (JsonElement mention : post.getAsJsonArray("mentions")) { JsonObject mentionObj = mention.getAsJsonObject(); String mentionedUserId = null; if (mentionObj.has("open_id")) { mentionedUserId = mentionObj.get("open_id").getAsString(); } else if (mentionObj.has("user_id")) { mentionedUserId = mentionObj.get("user_id").getAsString(); } else if (mentionObj.has("id")) { mentionedUserId = mentionObj.get("id").getAsString(); } if (mentionedUserId != null) { postData.mentionedUsers.add(getUserName(mentionedUserId)); } else if (mentionObj.has("name")) { postData.mentionedUsers.add(mentionObj.get("name").getAsString()); } } } // 内容处理 if (post.has("content")) { // 解析富文本内容为纯文本 String contentJson = post.get("content").getAsString(); postData.content = parseRichTextToPlainText(contentJson); } // 时间处理逻辑 if (post.has("create_time")) { try { JsonElement elem = post.get("create_time"); if (elem.isJsonPrimitive()) { if (elem.getAsJsonPrimitive().isString()) { String timeStr = elem.getAsString(); // 尝试多种格式解析 postData.publishTimeUnix = parseTimeToUnixMillis(timeStr); logger.info("解析发布时间: {} → {}", timeStr, postData.publishTimeUnix); } else if (elem.getAsJsonPrimitive().isNumber()) { postData.publishTimeUnix = elem.getAsLong() * 1000; logger.info("解析发布时间戳: {}", postData.publishTimeUnix); } } } catch (Exception e) { logger.error("解析create_time失败", e); } } } } } catch (Exception e) { logger.error("解析帖子详情失败", e); throw new IOException("解析帖子详情失败: " + e.getMessage()); } } // 发布时间解析方法 private long parseTimeToUnixMillis(String timeStr) { if (timeStr == null || timeStr.isEmpty()) { return 0; } // 1. 尝试解析为数字(秒级时间戳) try { long seconds = Long.parseLong(timeStr); return seconds * 1000; } catch (NumberFormatException e) { // 不是数字,继续尝试其他格式 } // 2. 尝试多种日期格式 for (DateTimeFormatter formatter : DATE_TIME_FORMATTERS) { try { // 尝试带时区解析 if (formatter == DateTimeFormatter.ISO_OFFSET_DATE_TIME) { ZonedDateTime zdt = ZonedDateTime.parse(timeStr, formatter); return zdt.toInstant().toEpochMilli(); } // 尝试本地时间解析(默认时区) LocalDateTime ldt = LocalDateTime.parse(timeStr, formatter); return ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); } catch (DateTimeParseException e) { } } // 3. 尝试其他可能的格式 try { // 尝试解析为毫秒级时间戳 return Long.parseLong(timeStr); } catch (NumberFormatException e) { // 解析失败 } logger.warn("无法解析时间字符串: {}", timeStr); return 0; } // 解析飞书富文本格式为纯文本 private String parseRichTextToPlainText(String richTextJson) { try { if (richTextJson == null || richTextJson.isEmpty()) { return ""; } JsonArray blocks = jsonParser.parse(richTextJson).getAsJsonArray(); StringBuilder plainText = new StringBuilder(); for (JsonElement block : blocks) { if (block.isJsonArray()) { JsonArray elements = block.getAsJsonArray(); for (JsonElement element : elements) { if (element.isJsonObject()) { JsonObject obj = element.getAsJsonObject(); if (obj.has("tag") && obj.has("text")) { String tag = obj.get("tag").getAsString(); String text = obj.get("text").getAsString(); // 处理不同类型的标签 if ("text".equals(tag)) { plainText.append(text); } else if ("hashtag".equals(tag)) { // 保留话题标签的文本内容 plainText.append("#").append(text).append(" "); } // 可以根据需要添加更多标签类型的处理 } } } // 块之间添加换行 plainText.append("\n"); } } return plainText.toString().trim(); } catch (Exception e) { logger.error("解析富文本失败: {}", richTextJson, e); // 如果解析失败,返回原始内容 return richTextJson; } } // 更新帖子热度值 private void updatePopularityScore(PostData postData) { double newScore = postData.likeCount * 1.0 - postData.dislikeCount * 0.5; double oldScore = postData.popularityScore; postData.popularityScore = newScore; logger.info("帖子 {} 热度更新: {} → {}", postData.postId, oldScore, newScore); } // 安排报告任务(每10分钟生成一次报告并清理缓存) public void scheduleWeeklyReport() { // 立即执行第一次报告生成和缓存清理 long initialDelay = 0; long period = 10; // 10分钟间隔 // 每10分钟生成报告 scheduler.scheduleAtFixedRate(() -> { try { logger.info("开始执行定期报告生成"); generateWeeklyReport(); } catch (Exception e) { logger.error("生成报告失败", e); } }, initialDelay, period, TimeUnit.MINUTES); // 每10分钟清理用户缓存 scheduler.scheduleAtFixedRate(() -> { logger.info("开始清理用户缓存"); int cacheSize = userCache.size(); userCache.clear(); logger.info("用户缓存已清除,共清理 {} 条记录", cacheSize); }, initialDelay, period, TimeUnit.MINUTES); LocalDateTime firstExecution = LocalDateTime.now().plusMinutes(initialDelay); logger.info("任务已安排:报告生成和缓存清理将于 {} 开始执行,每 {} 分钟运行一次", firstExecution.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), period); } // 生成周报并同步至多维表格 private void generateWeeklyReport() throws IOException { logger.info("开始生成周报..."); long insertionTime = System.currentTimeMillis(); // 获取统一的插入时间 List<PostData> topPosts = postDataMap.values().stream() .sorted(Comparator.comparingDouble(p -> -p.popularityScore)) .limit(3) .collect(Collectors.toList()); if (topPosts.isEmpty()) { logger.info("本周没有帖子数据"); return; } List<Map<String, Object>> records = new ArrayList<>(); for (int i = 0; i < topPosts.size(); i++) { PostData post = topPosts.get(i); Map<String, Object> record = new HashMap<>(); Map<String, Object> fields = new HashMap<>(); fields.put("板块信息", "夸一夸"); fields.put("帖子正文", post.content); fields.put("被@的人", String.join(", ", post.mentionedUsers)); fields.put("排名", i + 1); fields.put("综合热度值", post.popularityScore); fields.put("点赞数量", post.likeCount); fields.put("作者", post.author); // 使用帖子自身的发布时间 fields.put("发布时间", post.publishTimeUnix); record.put("fields", fields); records.add(record); logger.info("添加记录: 帖子ID={}, 作者={}, 发布时间={}", post.postId, post.author, post.publishTimeUnix); } insertRecordsToBitable(records); postDataMap.clear(); logger.info("周报生成完成,已同步至多维表格"); } // 插入记录到多维表格(增强错误处理) private void insertRecordsToBitable(List<Map<String, Object>> records) throws IOException { // 检查令牌有效性 if (!isTokenValid()) { logger.warn("访问令牌即将过期,正在刷新..."); refreshAccessToken(); } String url = "https://open.feishu.cn/open-apis/bitable/v1/apps/" + BITABLE_APP_TOKEN + "/tables/" + BITABLE_TABLE_ID + "/records/batch_create"; Map<String, Object> requestBody = new HashMap<>(); requestBody.put("records", records); MediaType JSON = MediaType.get("application/json; charset=utf-8"); okhttp3.RequestBody body = okhttp3.RequestBody.create(gson.toJson(requestBody), JSON); Request request = new Request.Builder() .url(url) .header("Authorization", "Bearer " + tenantAccessToken) .post(body) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { String errorBody = response.body().string(); logger.error("多维表格插入失败: HTTP {} - {}", response.code(), errorBody); // 处理403错误:尝试刷新令牌并重试 if (response.code() == 403) { logger.warn("访问令牌可能失效,尝试刷新并重试..."); refreshAccessToken(); Request newRequest = request.newBuilder() .header("Authorization", "Bearer " + tenantAccessToken) .build(); try (Response retryResponse = httpClient.newCall(newRequest).execute()) { if (!retryResponse.isSuccessful()) { String retryErrorBody = retryResponse.body().string(); logger.error("重试插入失败: HTTP {} - {}", retryResponse.code(), retryErrorBody); throw new IOException("重试插入失败: HTTP " + retryResponse.code()); } logger.info("重试插入成功"); return; } } throw new IOException("插入多维表格失败: HTTP " + response.code()); } String responseData = response.body().string(); logger.info("插入多维表格成功: {}", responseData); } } // 错误响应 private Map<String, Object> createErrorResponse(int code, String msg) { Map<String, Object> result = new HashMap<>(); result.put("code", code); result.put("msg", msg); return result; } // 成功响应 private Map<String, Object> createSuccessResponse() { Map<String, Object> result = new HashMap<>(); result.put("code", 0); result.put("msg", "success"); return result; } } 目前接口返回200
最新发布
06-18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值