feat: 챗봇 신고 페이지 AI 백엔드 추가 및 기타 개선
- ai-service: 챗봇 분석/요약 엔드포인트 추가 (chatbot.py, chatbot_service.py) - tkreport: 챗봇 신고 페이지 (chat-report.html/js/css), nginx ai-api 프록시 - tkreport: 이미지 업로드 서비스 개선, M-Project 연동 신고자 정보 전달 - system1: TBM 작업보고서 UI 개선 - TKQC: 관리함/수신함 기능 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
RUN apk add --no-cache imagemagick libheif imagemagick-heic imagemagick-jpeg
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
@@ -199,6 +199,7 @@ exports.createReport = async (req, res) => {
|
||||
|
||||
// 부적합 유형이면 System 3(tkqc)으로 비동기 전달
|
||||
try {
|
||||
console.log(`[System3 연동] report_id=${reportId}, category_type=${categoryType}`);
|
||||
if (catInfo && catInfo.category_type === 'nonconformity') {
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
@@ -209,8 +210,9 @@ exports.createReport = async (req, res) => {
|
||||
const filePath = path.join(__dirname, '..', p);
|
||||
const buf = await fs.readFile(filePath);
|
||||
photoBase64List.push(`data:image/jpeg;base64,${buf.toString('base64')}`);
|
||||
console.log(`[System3 연동] 사진 읽기 성공: ${p} (${buf.length} bytes)`);
|
||||
} catch (readErr) {
|
||||
console.error('사진 파일 읽기 실패:', p, readErr.message);
|
||||
console.error('[System3 연동] 사진 파일 읽기 실패:', p, readErr.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,27 +225,32 @@ exports.createReport = async (req, res) => {
|
||||
if (locationParts.workplace_name) locationInfo += ` - ${locationParts.workplace_name}`;
|
||||
}
|
||||
} catch (locErr) {
|
||||
console.error('위치 정보 조회 실패:', locErr.message);
|
||||
console.error('[System3 연동] 위치 정보 조회 실패:', locErr.message);
|
||||
}
|
||||
}
|
||||
|
||||
const originalToken = (req.headers['authorization'] || '').replace('Bearer ', '');
|
||||
console.log(`[System3 연동] sendToMProject 호출: category=${catInfo.category_name}, photos=${photoBase64List.length}장, reporter=${req.user.username}`);
|
||||
const result = await mProjectService.sendToMProject({
|
||||
category: catInfo.category_name,
|
||||
description: additional_description || catInfo.category_name,
|
||||
reporter_name: req.user.name || req.user.username,
|
||||
reporter_username: req.user.username || req.user.sub,
|
||||
reporter_role: req.user.role || 'user',
|
||||
tk_issue_id: reportId,
|
||||
project_id: project_id || null,
|
||||
location_info: locationInfo,
|
||||
photos: photoBase64List,
|
||||
ssoToken: originalToken
|
||||
});
|
||||
console.log(`[System3 연동] 결과: ${JSON.stringify(result)}`);
|
||||
if (result.success && result.mProjectId) {
|
||||
await workIssueModel.updateMProjectId(reportId, result.mProjectId);
|
||||
console.log(`[System3 연동] m_project_id=${result.mProjectId} 업데이트 완료`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[System3 연동] 부적합 아님, 건너뜀`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('System3 연동 실패 (신고는 정상 저장됨):', e.message);
|
||||
console.error('[System3 연동 실패]', e.message, e.stack);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('신고 생성 에러:', error);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const crypto = require('crypto');
|
||||
const { execFileSync } = require('child_process');
|
||||
|
||||
// sharp는 선택적으로 사용 (설치되어 있지 않으면 리사이징 없이 저장)
|
||||
let sharp;
|
||||
@@ -110,6 +111,7 @@ async function saveBase64Image(base64String, prefix = 'issue', category = 'issue
|
||||
const filepath = path.join(uploadDir, filename);
|
||||
|
||||
// sharp가 설치되어 있으면 리사이징 및 최적화
|
||||
let saved = false;
|
||||
if (sharp) {
|
||||
try {
|
||||
await sharp(buffer)
|
||||
@@ -119,13 +121,27 @@ async function saveBase64Image(base64String, prefix = 'issue', category = 'issue
|
||||
})
|
||||
.jpeg({ quality: QUALITY })
|
||||
.toFile(filepath);
|
||||
saved = true;
|
||||
} catch (sharpError) {
|
||||
console.error('sharp 처리 실패, 원본 저장:', sharpError.message);
|
||||
// sharp 실패 시 원본 저장
|
||||
await fs.writeFile(filepath, buffer);
|
||||
console.warn('sharp 처리 실패:', sharpError.message);
|
||||
}
|
||||
} else {
|
||||
// sharp가 없으면 원본 그대로 저장
|
||||
}
|
||||
|
||||
// sharp 실패 시 ImageMagick으로 변환 시도 (HEIC 등)
|
||||
if (!saved) {
|
||||
try {
|
||||
const tmpInput = filepath + '.tmp';
|
||||
await fs.writeFile(tmpInput, buffer);
|
||||
execFileSync('magick', [tmpInput, '-resize', `${MAX_SIZE.width}x${MAX_SIZE.height}>`, '-quality', String(QUALITY), filepath]);
|
||||
await fs.unlink(tmpInput).catch(() => {});
|
||||
saved = true;
|
||||
} catch (magickError) {
|
||||
console.warn('ImageMagick 변환 실패:', magickError.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 모두 실패 시 원본 저장
|
||||
if (!saved) {
|
||||
await fs.writeFile(filepath, buffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
const logger = require('../utils/logger');
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
// M-Project API 설정
|
||||
const M_PROJECT_CONFIG = {
|
||||
@@ -183,20 +184,25 @@ async function sendToMProject(issueData) {
|
||||
category,
|
||||
description,
|
||||
reporter_name,
|
||||
reporter_username = null,
|
||||
reporter_role = 'user',
|
||||
project_name,
|
||||
project_id = null,
|
||||
tk_issue_id,
|
||||
location_info = null,
|
||||
photos = [],
|
||||
ssoToken = null,
|
||||
} = issueData;
|
||||
|
||||
logger.info('M-Project 연동 시작', { tk_issue_id, category });
|
||||
|
||||
// SSO 토큰이 있으면 원래 사용자로 전송, 없으면 api_service 토큰
|
||||
// 신고자 정보로 SSO JWT 토큰 직접 생성 (api_service 대신 실제 신고자로)
|
||||
let token;
|
||||
if (ssoToken) {
|
||||
token = ssoToken;
|
||||
if (reporter_username && process.env.JWT_SECRET) {
|
||||
token = jwt.sign(
|
||||
{ sub: reporter_username, name: reporter_name || reporter_username, role: reporter_role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '1h' }
|
||||
);
|
||||
} else {
|
||||
token = await getAuthToken();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user