适配高德地图,用户访问时用一个很小的Google资源作为探针,在中国大陆境内则默认用高德地图,在海外则默认用Google地图;用 Google Geocode 得到地理位置编码,在把地理位置编码提供给高德地图,解决 AMAP API 对直接用英文搜索地名支持很差的问题。不足之处:高德地图无法对海外城市提供路线规划(场景:用户在中国大陆,但旅行计划在在海外城市);同样的,谷歌地图对中国大陆城市的路线规划的支持也不好。
/** * Spring Security JWT filter that extracts and validates JWT from request headers */ @Slf4j @Component @RequiredArgsConstructor publicclassJwtFilterextendsOncePerRequestFilter{
// Extract and validate JWT from Authorization header: Bearer <JWT> String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { // Extract JWT from Bearer <JWT> String token = header.substring(7); try { Claims claims = jwtUtil.parse(token); String subject = claims.getSubject(); if (subject != null && SecurityContextHolder.getContext().getAuthentication() == null) { Long userId = Long.valueOf(subject); Integer tokenVersion = claims.get("version", Integer.class);
User user = userRepository.findById(userId).orElse(null); // Check whether token version matches (mismatch after password change) if (user != null && tokenVersion.equals(user.getTokenVersion())) { // Set the subject content (String userId) as the JWT principal // In controllers, you can get the principal from the authenticated user's token via @AuthenticationPrincipal (String userId) UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( subject, null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(auth); // Authenticated } else { SecurityContextHolder.clearContext(); } } } catch (RuntimeException e) { // If parsing fails, do not set Authentication; the request will be rejected at controller due to unauthenticated SecurityContextHolder.clearContext(); log.debug("Invalid token", e); } } chain.doFilter(request, response); } }
/** * 1. S3 single file upload * * @param in file input stream * @param originalFilename original filename * @return Return URL (https://{cdn}/{dir/yyyy/MM/xxx.png}) */ public String upload(InputStream in, String originalFilename, boolean isAvatar)throws Exception { // If it is an avatar, put it under elec5620-stage2/avatars String baseDir = isAvatar ? (dirName + "/avatars") : dirName;
// Directory and file name: rewrite the filename String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM")); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); String newFileName = UUID.randomUUID().toString().replace("-", "") + suffix; String objectKey = baseDir + "/" + dir + "/" + newFileName;
// Read into byte[] to get content-length ByteArrayOutputStream bos = new ByteArrayOutputStream(Math.max(32 * 1024, in.available())); in.transferTo(bos); byte[] bytes = bos.toByteArray();
String contentType = switch (suffix) { case".jpg", ".jpeg" -> "image/jpeg"; case".png" -> "image/png"; case".gif" -> "image/gif"; case".svg" -> "image/svg+xml"; case".webp" -> "image/webp"; default -> "application/octet-stream"; // Default type when file type is unknown };
long maxSize = 10 * 1024 * 1024L; long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1); if (contentLength > maxSize) { thrownew IllegalArgumentException("File too large, max size is 10 MB"); }
// Read into memory and enforce 10 MB limit try (InputStream in = response.body(); ByteArrayOutputStream bos = new ByteArrayOutputStream()) { byte[] buf = newbyte[8192]; long total = 0; int n; while ((n = in.read(buf)) != -1) { total += n; if (total > maxSize) { thrownew IllegalArgumentException("File too large, max size is 10 MB"); } bos.write(buf, 0, n); } String s3Url = upload(new ByteArrayInputStream(bos.toByteArray()), "temp" + suffix, isAvatar); log.info("Upload from {} to {}", imageUrl, s3Url); return s3Url; } }
/** * 2. List files in AWS S3 with the specified prefix (applicable when file count ≤ 1000) * * @param preFix Prefix (e.g., elec5620-stage2/2025) * @param size Maximum number to list (up to 1000) */ public List<String> listFiles(String preFix, int size)throws Exception { if (size > 1000) { size = 1000; log.warn("A maximum of 1000 files is allowed, it has been automatically adjusted to 1000"); }
/** * 3. Paginated listing of files in AWS S3 with the specified prefix (applicable when file count > 1000) * * @param preFix Prefix * @param pageSize Page size (max 1000) */ public List<String> listPageAllFiles(String preFix, int pageSize)throws Exception { if (pageSize > 1000) { pageSize = 1000; log.warn("A maximum of 1000 files every time is allowed, it has been automatically adjusted to 1000"); }
List<String> fileList = new ArrayList<>(); String continuationToken = null;
ListObjectsV2Response resp; do { var builder = ListObjectsV2Request.builder() .bucket(bucketName) .prefix(preFix) .maxKeys(pageSize); if (continuationToken != null) builder.continuationToken(continuationToken);
resp = s3Client.listObjectsV2(builder.build());
List<S3Object> contents = resp.contents(); if (ObjectUtil.isNotEmpty(contents)) { fileList.addAll(contents.stream().map(S3Object::key).toList()); } continuationToken = resp.nextContinuationToken(); } while (resp.isTruncated());
/** * 5. Batch delete multiple files (up to 1000 at a time; split into batches if exceeded) * * @param objectKeys List of keys to delete */ publicvoidbatchDeleteFiles(List<String> objectKeys)throws Exception { for (int i = 0; i < objectKeys.size(); i += 1000) { int end = Math.min(i + 1000, objectKeys.size()); List<String> subList = objectKeys.subList(i, end); log.info("Batch deleting files, current batch: {}, file count: {}", (i / 1000) + 1, subList.size());
List<ObjectIdentifier> ids = new ArrayList<>(subList.size()); for (String key : subList) { ids.add(ObjectIdentifier.builder().key(key).build()); }
/** * Send HTML email. * @param to * @param subject * @param html */ publicvoidsendHtml(String to, String subject, String html){ Mail mail = new Mail(); mail.setFrom(new Email(from)); mail.setSubject(subject);
Personalization p = new Personalization(); p.addTo(new Email(to)); mail.addPersonalization(p);
/** * Search N image urls. * @param q search keyword * @param n number of images to be returned * @param width width of the image * @param height height of the image * @return a list of Aws S3 URLs */ public List<String> getImgUrls(String q, int n, int width, int height){ return searchNImg(q, n).stream() .map(photo -> imgUrlFormat(photo, width, height)).toList(); }
/** * Search N image urls (default size: 1600x900). * @param q search keyword * @param n number of images to be returned * @return a list of Aws S3 URLs */ public List<String> getImgUrls(String q, int n){ return searchNImg(q, n).stream() .map(photo -> imgUrlFormat(photo, 1600, 900)).toList(); }
/** * Get N image Details (JSON) from Unsplash API. */ @SuppressWarnings("unchecked") private List<Map<String, Object>> searchNImg(String query, int n) { if(n > 10){ n = 10; log.warn("maximum number is 10, automatically adjusted to 10"); } if(n < 1){ thrownew IllegalArgumentException("number must be greater than 0"); } if (query == null || query.isBlank()) { thrownew IllegalArgumentException("query must not be blank"); } finalint count = n; Map<String, Object> body = unsplashWebClient.get() .uri(uri -> uri.path("/search/photos") .queryParam("query", query) .queryParam("per_page", count) .build()) .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToMono(Map.class) .block();