Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4218bea4c | ||
|
|
040801ac3b | ||
|
|
68c10979ff | ||
|
|
27bba95250 |
No files matched your search
@@ -0,0 +1,89 @@
|
|||||||
|
name: Build and Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
env:
|
||||||
|
APP_NAME: ahsarah-guide-server
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: server
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 21 (Corretto)
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: 21
|
||||||
|
distribution: corretto
|
||||||
|
cache: gradle
|
||||||
|
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: >
|
||||||
|
./gradlew bootJar -x test
|
||||||
|
-PartefactVersion="${{ gitea.event.release.tag_name }}"
|
||||||
|
-PbuildChannel=stable
|
||||||
|
-Pvendor=${{ vars.VENDOR }}
|
||||||
|
|
||||||
|
- name: Resolve JAR file path
|
||||||
|
id: jar
|
||||||
|
run: |
|
||||||
|
JAR_PATH=$(find build/libs -name '*.jar' | head -1)
|
||||||
|
echo "file=$JAR_PATH" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Upload JAR to Gitea Release
|
||||||
|
run: |
|
||||||
|
TAG="${{ gitea.event.release.tag_name }}"
|
||||||
|
FILE="${{ steps.jar.outputs.file }}"
|
||||||
|
ASSET_NAME="${APP_NAME}-${TAG}.jar"
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
|
-H "Content-Type: multipart/form-data" \
|
||||||
|
-F "attachment=@${FILE};filename=${ASSET_NAME}" \
|
||||||
|
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${{ gitea.event.release.id }}/assets?name=${ASSET_NAME}"
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate image tags
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
DOCKERHUB_USER="${{ vars.DOCKER_HUB_USERNAME }}"
|
||||||
|
echo "tag_version=${DOCKERHUB_USER}/${APP_NAME}:${{ gitea.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "tag_latest=${DOCKERHUB_USER}/${APP_NAME}:latest" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: server
|
||||||
|
file: server/Dockerfile.ci
|
||||||
|
build-args: |
|
||||||
|
JAR_FILE=${{ steps.jar.outputs.file }}
|
||||||
|
IMAGE_VERSION=${{ gitea.event.release.tag_name }}
|
||||||
|
IMAGE_VENDOR=${{ vars.VENDOR }}
|
||||||
|
BUILD_DATE=${{ gitea.event.release.published_at }}
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ steps.meta.outputs.tag_version }}
|
||||||
|
${{ steps.meta.outputs.tag_latest }}
|
||||||
|
|
||||||
|
- name: Push README to Docker Hub
|
||||||
|
uses: christian-korneck/update-container-description-action@v1
|
||||||
|
env:
|
||||||
|
DOCKER_USER: ${{ vars.DOCKER_HUB_USERNAME }}
|
||||||
|
DOCKER_PASS: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
destination_container_repo: ${{ vars.DOCKER_HUB_USERNAME }}/${{ env.APP_NAME }}
|
||||||
|
provider: dockerhub
|
||||||
|
readme_file: server/README.md
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
name: Build and Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build release archive
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: web
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 11
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build release archive
|
||||||
|
run: pnpm build:tar
|
||||||
|
|
||||||
|
- name: Clean up previous build artifacts on server
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ vars.DEPLOY_HOST }}
|
||||||
|
username: ${{ vars.DEPLOY_USER }}
|
||||||
|
port: ${{ vars.DEPLOY_PORT }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
command: rm -rf /tmp/dist.tar.gz /tmp/dist.tar.gz/
|
||||||
|
|
||||||
|
- name: Upload artifact to server
|
||||||
|
uses: appleboy/scp-action@v0.1.7
|
||||||
|
with:
|
||||||
|
host: ${{ vars.DEPLOY_HOST }}
|
||||||
|
username: ${{ vars.DEPLOY_USER }}
|
||||||
|
port: ${{ vars.DEPLOY_PORT }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
source: "dist.tar.gz"
|
||||||
|
target: "/tmp/"
|
||||||
|
|
||||||
|
upload-release-asset:
|
||||||
|
name: Upload to Gitea Release
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download artifact from server
|
||||||
|
env:
|
||||||
|
DEPLOY_HOST: ${{ vars.DEPLOY_HOST }}
|
||||||
|
DEPLOY_USER: ${{ vars.DEPLOY_USER }}
|
||||||
|
DEPLOY_PORT: ${{ vars.DEPLOY_PORT }}
|
||||||
|
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
SSH_DIR="${RUNNER_TEMP}/ssh"
|
||||||
|
mkdir -p "$SSH_DIR"
|
||||||
|
chmod 700 "$SSH_DIR"
|
||||||
|
printf '%s\n' "$DEPLOY_SSH_KEY" > "$SSH_DIR/key"
|
||||||
|
chmod 600 "$SSH_DIR/key"
|
||||||
|
scp -i "$SSH_DIR/key" \
|
||||||
|
-P "$DEPLOY_PORT" \
|
||||||
|
-o StrictHostKeyChecking=accept-new \
|
||||||
|
-o UserKnownHostsFile="$SSH_DIR/known_hosts" \
|
||||||
|
"${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/dist.tar.gz" \
|
||||||
|
./dist.tar.gz
|
||||||
|
rm -rf "$SSH_DIR"
|
||||||
|
|
||||||
|
- name: Upload release asset via Gitea API
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
RELEASE_ID=$(jq -r '.release.id' "$GITEA_EVENT_PATH")
|
||||||
|
URL="${GITEA_SERVER_URL}/api/v1/repos/${GITEA_REPOSITORY}/releases/${RELEASE_ID}/assets?name=dist.tar.gz"
|
||||||
|
curl -fsSL \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/gzip" \
|
||||||
|
--data-binary "@dist.tar.gz" \
|
||||||
|
"${URL}"
|
||||||
|
|
||||||
|
deploy-to-server:
|
||||||
|
name: Deploy to onixbyte.cn
|
||||||
|
needs: [build, upload-release-asset]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Extract archive and deploy
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ vars.DEPLOY_HOST }}
|
||||||
|
username: ${{ vars.DEPLOY_USER }}
|
||||||
|
port: ${{ vars.DEPLOY_PORT }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
script_stop: true
|
||||||
|
script: |
|
||||||
|
set -e
|
||||||
|
DEPLOY_PATH="${{ vars.DEPLOY_PATH }}"
|
||||||
|
mkdir -p "$DEPLOY_PATH"
|
||||||
|
rm -rf "$DEPLOY_PATH"/*
|
||||||
|
tar -xzf /tmp/dist.tar.gz -C "$DEPLOY_PATH" --strip-components=1
|
||||||
|
chown -R caddy:caddy "$DEPLOY_PATH"
|
||||||
|
rm -f /tmp/dist.tar.gz
|
||||||
@@ -29,47 +29,6 @@ spring:
|
|||||||
# Uncomment if the server requires a password.
|
# Uncomment if the server requires a password.
|
||||||
#
|
#
|
||||||
# password: qwerty
|
# password: qwerty
|
||||||
mail:
|
|
||||||
# SMTP endpoint.
|
|
||||||
host: smtp.example.com
|
|
||||||
|
|
||||||
# SMTP port.
|
|
||||||
#
|
|
||||||
# 465 uses implicit TLS (SMTPS). If outbound 465 is blocked on the network, switch to 587 and
|
|
||||||
# replace the `ssl` property below with `starttls.enable` instead.
|
|
||||||
port: 465
|
|
||||||
|
|
||||||
# Sending address, which is also used as the `From` address of every dispatched message.
|
|
||||||
#
|
|
||||||
# It MUST belong to a verified sending domain in the Tencent Cloud Email console.
|
|
||||||
username: app@example.com
|
|
||||||
|
|
||||||
# SMTP password of the sending address above, as configured in the Tencent Cloud Email console.
|
|
||||||
password: 123456
|
|
||||||
|
|
||||||
# Encoding applied to the message headers and body.
|
|
||||||
default-encoding: UTF-8
|
|
||||||
|
|
||||||
properties:
|
|
||||||
mail:
|
|
||||||
smtp:
|
|
||||||
# Whether the SMTP server requires authentication.
|
|
||||||
auth: true
|
|
||||||
|
|
||||||
# Implicit TLS, required by port 465.
|
|
||||||
#
|
|
||||||
# When using port 587 + STARTTLS instead, drop this property, then enable STARTTLS in
|
|
||||||
# its place:
|
|
||||||
#
|
|
||||||
# starttls:
|
|
||||||
# enable: true
|
|
||||||
ssl:
|
|
||||||
enable: true
|
|
||||||
|
|
||||||
# Fail fast instead of hanging when the SMTP server is unreachable.
|
|
||||||
connectiontimeout: 10000
|
|
||||||
timeout: 10000
|
|
||||||
writetimeout: 10000
|
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
pattern:
|
pattern:
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package com.onixbyte.ahsarahguide.config;
|
package com.onixbyte.ahsarahguide.config;
|
||||||
|
|
||||||
import com.onixbyte.ahsarahguide.shared.JacksonRedisSerialiser;
|
import com.onixbyte.ahsarahguide.shared.JacksonRedisSerialiser;
|
||||||
import org.springframework.cache.CacheManager;
|
|
||||||
import org.springframework.cache.annotation.EnableCaching;
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
@@ -41,24 +39,21 @@ import java.time.Duration;
|
|||||||
public class CacheConfig {
|
public class CacheConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the application's primary cache manager, backed by Redis.
|
* Creates a custom Redis cache manager with JSON serialisation support.
|
||||||
* <p>
|
* <p>
|
||||||
* This manager is marked {@link Primary}, so it is the one selected for any
|
* This method configures a {@link RedisCacheManager} that uses string serialisation for cache
|
||||||
* {@code @Cacheable} or {@code @CacheEvict} annotation that does not name a manager
|
* keys and {@link GenericJackson2JsonRedisSerializer} for cache values. This setup ensures that
|
||||||
* explicitly, as well as for any {@link CacheManager} that is injected by type.
|
* complex objects can be stored and retrieved from Redis cache whilst maintaining readability
|
||||||
* <p>
|
* and compatibility with JSON-based systems.
|
||||||
* Cache entries are keyed using string serialisation and values are stored as JSON via
|
|
||||||
* {@link JacksonRedisSerialiser}. Every entry expires after two hours, making this manager
|
|
||||||
* suitable for data that is expensive to compute but not required to survive for long.
|
|
||||||
*
|
*
|
||||||
* @param connectionFactory the Redis connection factory used to establish connections
|
* @param connectionFactory the Redis connection factory used to establish connections
|
||||||
* @return a fully configured {@link CacheManager} with a two-hour default entry TTL
|
* @return a configured {@link RedisCacheManager} with custom serialisation settings
|
||||||
* @see #longTermCacheManager(RedisConnectionFactory)
|
|
||||||
* @see RedisCacheManager
|
* @see RedisCacheManager
|
||||||
|
* @see GenericJackson2JsonRedisSerializer
|
||||||
|
* @see RedisSerializationContext
|
||||||
*/
|
*/
|
||||||
@Primary
|
|
||||||
@Bean
|
@Bean
|
||||||
public CacheManager defaultCacheManager(
|
public RedisCacheManager cacheManager(
|
||||||
RedisConnectionFactory connectionFactory
|
RedisConnectionFactory connectionFactory
|
||||||
) {
|
) {
|
||||||
var _keySerializer = RedisSerializer.string();
|
var _keySerializer = RedisSerializer.string();
|
||||||
@@ -68,40 +63,7 @@ public class CacheConfig {
|
|||||||
.fromSerializer(_keySerializer))
|
.fromSerializer(_keySerializer))
|
||||||
.serializeValuesWith(RedisSerializationContext.SerializationPair
|
.serializeValuesWith(RedisSerializationContext.SerializationPair
|
||||||
.fromSerializer(JacksonRedisSerialiser.INSTANCE))
|
.fromSerializer(JacksonRedisSerialiser.INSTANCE))
|
||||||
.entryTtl(Duration.ofHours(2L));
|
.entryTtl(Duration.ofMinutes(90L));
|
||||||
|
|
||||||
return RedisCacheManager.RedisCacheManagerBuilder
|
|
||||||
.fromConnectionFactory(connectionFactory)
|
|
||||||
.cacheDefaults(cacheConfiguration)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a secondary cache manager for data that must be retained for longer.
|
|
||||||
* <p>
|
|
||||||
* This manager is not {@link Primary}; it must be requested by name wherever it is needed,
|
|
||||||
* as in {@code @Cacheable(cacheManager = "longTermCacheManager")}.
|
|
||||||
* <p>
|
|
||||||
* Serialisation is configured identically to {@link #defaultCacheManager}, but entries
|
|
||||||
* expire after one day rather than two hours.
|
|
||||||
*
|
|
||||||
* @param connectionFactory the Redis connection factory used to establish connections
|
|
||||||
* @return a fully configured {@link CacheManager} with a one-day default entry TTL
|
|
||||||
* @see #defaultCacheManager(RedisConnectionFactory)
|
|
||||||
* @see RedisCacheManager
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public CacheManager longTermCacheManager(
|
|
||||||
RedisConnectionFactory connectionFactory
|
|
||||||
) {
|
|
||||||
var _keySerializer = RedisSerializer.string();
|
|
||||||
|
|
||||||
var cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
|
|
||||||
.serializeKeysWith(RedisSerializationContext.SerializationPair
|
|
||||||
.fromSerializer(_keySerializer))
|
|
||||||
.serializeValuesWith(RedisSerializationContext.SerializationPair
|
|
||||||
.fromSerializer(JacksonRedisSerialiser.INSTANCE))
|
|
||||||
.entryTtl(Duration.ofDays(1L));
|
|
||||||
|
|
||||||
return RedisCacheManager.RedisCacheManagerBuilder
|
return RedisCacheManager.RedisCacheManagerBuilder
|
||||||
.fromConnectionFactory(connectionFactory)
|
.fromConnectionFactory(connectionFactory)
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||||
import com.onixbyte.ahsarahguide.domain.dto.DailyPasswordResponse;
|
import com.onixbyte.ahsarahguide.domain.dto.DailyPasswordResponse;
|
||||||
|
import com.onixbyte.ahsarahguide.exeption.InternalServerErrorException;
|
||||||
import com.onixbyte.ahsarahguide.shared.JacksonModules;
|
import com.onixbyte.ahsarahguide.shared.JacksonModules;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.client.RestClient;
|
import org.springframework.web.client.RestClient;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,13 +24,15 @@ import java.util.Objects;
|
|||||||
@Component
|
@Component
|
||||||
public class DailyPasswordManager {
|
public class DailyPasswordManager {
|
||||||
|
|
||||||
private static final String CACHE_NAME = "daily-password";
|
private static final String CACHE_KEY_PREFIX = "daily-password:";
|
||||||
|
|
||||||
private final RestClient restClient;
|
private final RestClient restClient;
|
||||||
|
private final RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public DailyPasswordManager(
|
public DailyPasswordManager(
|
||||||
RestClient.Builder restClientBuilder
|
RestClient.Builder restClientBuilder,
|
||||||
|
RedisTemplate<String, Object> redisTemplate
|
||||||
) {
|
) {
|
||||||
var snakeCaseMapper = new ObjectMapper();
|
var snakeCaseMapper = new ObjectMapper();
|
||||||
snakeCaseMapper.setPropertyNamingStrategy(
|
snakeCaseMapper.setPropertyNamingStrategy(
|
||||||
@@ -47,23 +50,22 @@ public class DailyPasswordManager {
|
|||||||
new MappingJackson2HttpMessageConverter(snakeCaseMapper));
|
new MappingJackson2HttpMessageConverter(snakeCaseMapper));
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the daily password from cache or generates a new one.
|
* Retrieves the daily password from cache or generates a new one.
|
||||||
* @return the daily password response
|
* @return the daily password response
|
||||||
*/
|
*/
|
||||||
@Cacheable(
|
|
||||||
cacheNames = CACHE_NAME,
|
|
||||||
key = "T(java.time.LocalDate).now().toString()",
|
|
||||||
sync = true,
|
|
||||||
unless = "#result == null",
|
|
||||||
cacheManager = "longTermCacheManager"
|
|
||||||
)
|
|
||||||
public DailyPasswordResponse getDailyPassword() {
|
public DailyPasswordResponse getDailyPassword() {
|
||||||
DailyPasswordResponse response;
|
var key = CACHE_KEY_PREFIX + LocalDate.now();
|
||||||
try {
|
|
||||||
response = restClient.get()
|
var cached = redisTemplate.opsForValue().get(key);
|
||||||
|
if (cached != null) {
|
||||||
|
return (DailyPasswordResponse) cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = restClient.get()
|
||||||
.uri((uriBuilder) -> uriBuilder
|
.uri((uriBuilder) -> uriBuilder
|
||||||
.path("/sjzmm")
|
.path("/sjzmm")
|
||||||
.queryParam("ckey", "")
|
.queryParam("ckey", "")
|
||||||
@@ -71,15 +73,12 @@ public class DailyPasswordManager {
|
|||||||
.build())
|
.build())
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.body(DailyPasswordResponse.class);
|
.body(DailyPasswordResponse.class);
|
||||||
} catch (Exception e) {
|
|
||||||
// Catch network errors, timeouts, or 4xx/5xx responses from the upstream service
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "无法获取每日密码数据。", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Objects.isNull(response)) {
|
if (Objects.isNull(response)) {
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_GATEWAY, "无法获取每日密码数据。");
|
throw new InternalServerErrorException("暂无每日密码数据。");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redisTemplate.opsForValue().set(key, response, Duration.ofDays(1L));
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user