Add cuisine subcategory filter, fix remap logic, and add OKE deployment manifests
- Add 파인다이닝/코스 cuisine type to 한식/일식/중식/양식 categories - Change cuisine filter from flat list to grouped optgroup with subcategories - Fix remap-foods/remap-cuisine: add jdbcType=CLOB, fix CLOB LISTAGG, improve retry logic (3 attempts, batch size 5), add error logging - Add OKE deployment: Dockerfiles, K8s manifests, deploy.sh, deployment guide - Add Next.js standalone output for Docker builds Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
16
backend-java/Dockerfile
Normal file
16
backend-java/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
# ── Build stage ──
|
||||
FROM eclipse-temurin:21-jdk AS build
|
||||
WORKDIR /app
|
||||
COPY gradlew settings.gradle build.gradle ./
|
||||
COPY gradle/ gradle/
|
||||
RUN chmod +x gradlew && ./gradlew dependencies --no-daemon || true
|
||||
COPY src/ src/
|
||||
RUN ./gradlew bootJar -x test --no-daemon
|
||||
|
||||
# ── Runtime stage ──
|
||||
FROM eclipse-temurin:21-jre
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/build/libs/*.jar app.jar
|
||||
EXPOSE 8000
|
||||
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"
|
||||
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
|
||||
@@ -138,20 +138,31 @@ public class VideoSseController {
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: retry missed
|
||||
// Pass 2: retry missed (up to 3 attempts with smaller batches)
|
||||
if (!allMissed.isEmpty()) {
|
||||
emit(emitter, Map.of("type", "retry", "missed", allMissed.size()));
|
||||
for (int i = 0; i < allMissed.size(); i += 10) {
|
||||
var batch = allMissed.subList(i, Math.min(i + 10, allMissed.size()));
|
||||
try {
|
||||
var result = applyRemapBatch(batch);
|
||||
updated += result.updated;
|
||||
} catch (Exception ignored) {}
|
||||
for (int attempt = 0; attempt < 3 && !allMissed.isEmpty(); attempt++) {
|
||||
var retryList = new ArrayList<>(allMissed);
|
||||
allMissed.clear();
|
||||
for (int i = 0; i < retryList.size(); i += 5) {
|
||||
var batch = retryList.subList(i, Math.min(i + 5, retryList.size()));
|
||||
try {
|
||||
var result = applyRemapBatch(batch);
|
||||
updated += result.updated;
|
||||
allMissed.addAll(result.missed);
|
||||
} catch (Exception e) {
|
||||
log.warn("Remap cuisine retry failed (attempt {}): {}", attempt + 1, e.getMessage());
|
||||
allMissed.addAll(batch);
|
||||
}
|
||||
}
|
||||
if (!allMissed.isEmpty()) {
|
||||
emit(emitter, Map.of("type", "retry", "attempt", attempt + 2, "missed", allMissed.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.flush();
|
||||
emit(emitter, Map.of("type", "complete", "total", total, "updated", updated));
|
||||
emit(emitter, Map.of("type", "complete", "total", total, "updated", updated, "missed", allMissed.size()));
|
||||
emitter.complete();
|
||||
} catch (Exception e) {
|
||||
emitter.completeWithError(e);
|
||||
@@ -172,7 +183,9 @@ public class VideoSseController {
|
||||
var rows = restaurantService.findForRemapFoods();
|
||||
rows = rows.stream().map(r -> {
|
||||
var m = JsonUtil.lowerKeys(r);
|
||||
m.put("foods", JsonUtil.parseStringList(m.get("foods_mentioned")));
|
||||
// foods_mentioned is now TO_CHAR'd in SQL, parse as string
|
||||
Object fm = m.get("foods_mentioned");
|
||||
m.put("foods", JsonUtil.parseStringList(fm));
|
||||
return m;
|
||||
}).toList();
|
||||
|
||||
@@ -191,23 +204,36 @@ public class VideoSseController {
|
||||
emit(emitter, Map.of("type", "batch_done", "current", Math.min(i + BATCH, total), "total", total, "updated", updated));
|
||||
} catch (Exception e) {
|
||||
allMissed.addAll(batch);
|
||||
log.warn("Remap foods batch error at {}: {}", i, e.getMessage());
|
||||
emit(emitter, Map.of("type", "error", "message", e.getMessage(), "current", i));
|
||||
}
|
||||
}
|
||||
|
||||
// Retry missed (up to 3 attempts with smaller batches)
|
||||
if (!allMissed.isEmpty()) {
|
||||
emit(emitter, Map.of("type", "retry", "missed", allMissed.size()));
|
||||
for (int i = 0; i < allMissed.size(); i += 10) {
|
||||
var batch = allMissed.subList(i, Math.min(i + 10, allMissed.size()));
|
||||
try {
|
||||
var r = applyFoodsBatch(batch);
|
||||
updated += r.updated;
|
||||
} catch (Exception ignored) {}
|
||||
for (int attempt = 0; attempt < 3 && !allMissed.isEmpty(); attempt++) {
|
||||
var retryList = new ArrayList<>(allMissed);
|
||||
allMissed.clear();
|
||||
for (int i = 0; i < retryList.size(); i += 5) {
|
||||
var batch = retryList.subList(i, Math.min(i + 5, retryList.size()));
|
||||
try {
|
||||
var r = applyFoodsBatch(batch);
|
||||
updated += r.updated;
|
||||
allMissed.addAll(r.missed);
|
||||
} catch (Exception e) {
|
||||
log.warn("Remap foods retry failed (attempt {}): {}", attempt + 1, e.getMessage());
|
||||
allMissed.addAll(batch);
|
||||
}
|
||||
}
|
||||
if (!allMissed.isEmpty()) {
|
||||
emit(emitter, Map.of("type", "retry", "attempt", attempt + 2, "missed", allMissed.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.flush();
|
||||
emit(emitter, Map.of("type", "complete", "total", total, "updated", updated));
|
||||
emit(emitter, Map.of("type", "complete", "total", total, "updated", updated, "missed", allMissed.size()));
|
||||
emitter.complete();
|
||||
} catch (Exception e) {
|
||||
emitter.completeWithError(e);
|
||||
|
||||
@@ -15,12 +15,12 @@ public final class CuisineTypes {
|
||||
"한식|백반/한정식", "한식|국밥/해장국", "한식|찌개/전골/탕", "한식|삼겹살/돼지구이",
|
||||
"한식|소고기/한우구이", "한식|곱창/막창", "한식|닭/오리구이", "한식|족발/보쌈",
|
||||
"한식|회/횟집", "한식|해산물", "한식|분식", "한식|면", "한식|죽/죽집",
|
||||
"한식|순대/순대국", "한식|장어/민물", "한식|주점/포차",
|
||||
"한식|순대/순대국", "한식|장어/민물", "한식|주점/포차", "한식|파인다이닝/코스",
|
||||
"일식|스시/오마카세", "일식|라멘", "일식|돈카츠", "일식|텐동/튀김",
|
||||
"일식|이자카야", "일식|야키니쿠", "일식|카레", "일식|소바/우동",
|
||||
"중식|중화요리", "중식|마라/훠궈", "중식|딤섬/만두", "중식|양꼬치",
|
||||
"일식|이자카야", "일식|야키니쿠", "일식|카레", "일식|소바/우동", "일식|파인다이닝/코스",
|
||||
"중식|중화요리", "중식|마라/훠궈", "중식|딤섬/만두", "중식|양꼬치", "중식|파인다이닝/코스",
|
||||
"양식|파스타/이탈리안", "양식|스테이크", "양식|햄버거", "양식|피자",
|
||||
"양식|프렌치", "양식|바베큐", "양식|브런치", "양식|비건/샐러드",
|
||||
"양식|프렌치", "양식|바베큐", "양식|브런치", "양식|비건/샐러드", "양식|파인다이닝/코스",
|
||||
"아시아|베트남", "아시아|태국", "아시아|인도/중동", "아시아|동남아기타",
|
||||
"기타|치킨", "기타|카페/디저트", "기타|베이커리", "기타|뷔페", "기타|퓨전"
|
||||
);
|
||||
|
||||
@@ -208,12 +208,13 @@
|
||||
</update>
|
||||
|
||||
<update id="updateFoodsMentioned">
|
||||
UPDATE video_restaurants SET foods_mentioned = #{foods} WHERE id = #{id}
|
||||
UPDATE video_restaurants SET foods_mentioned = #{foods,jdbcType=CLOB} WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="findForRemapCuisine" resultType="map">
|
||||
SELECT r.id, r.name, r.cuisine_type,
|
||||
(SELECT LISTAGG(vr.foods_mentioned, '|') WITHIN GROUP (ORDER BY vr.id)
|
||||
(SELECT LISTAGG(TO_CHAR(DBMS_LOB.SUBSTR(vr.foods_mentioned, 500, 1)), '|')
|
||||
WITHIN GROUP (ORDER BY vr.id)
|
||||
FROM video_restaurants vr WHERE vr.restaurant_id = r.id) AS foods
|
||||
FROM restaurants r
|
||||
WHERE EXISTS (SELECT 1 FROM video_restaurants vr2 WHERE vr2.restaurant_id = r.id)
|
||||
@@ -221,7 +222,9 @@
|
||||
</select>
|
||||
|
||||
<select id="findForRemapFoods" resultType="map">
|
||||
SELECT vr.id, r.name, r.cuisine_type, vr.foods_mentioned, v.title
|
||||
SELECT vr.id, r.name, r.cuisine_type,
|
||||
TO_CHAR(vr.foods_mentioned) AS foods_mentioned,
|
||||
v.title
|
||||
FROM video_restaurants vr
|
||||
JOIN restaurants r ON r.id = vr.restaurant_id
|
||||
JOIN videos v ON v.id = vr.video_id
|
||||
|
||||
Reference in New Issue
Block a user