diff --git a/manual/17_지오정보기술_홈페이지_개발가이드.md b/manual/17_지오정보기술_홈페이지_개발가이드.md new file mode 100644 index 00000000..0fe4a193 --- /dev/null +++ b/manual/17_지오정보기술_홈페이지_개발가이드.md @@ -0,0 +1,186 @@ +# (주)지오정보기술 홈페이지 개발 가이드 + +> **프로젝트 코드:** ZIO-WEB-2026 +> **기술 스택:** Spring Boot 3.2 + React 18 + SQLite +> **GUARDiA PMS:** http://localhost:8001/si (프로젝트 ID=1) + +--- + +## 1. 프로젝트 구조 + +``` +workspace/zioinfo-web/ +├── backend/ # Spring Boot 백엔드 (포트 8080) +│ ├── pom.xml # Maven 의존성 (SQLite, JPA, Mail) +│ ├── src/main/ +│ │ ├── java/kr/co/zioinfo/web/ +│ │ │ ├── ZioinfoWebApplication.java +│ │ │ ├── controller/ApiController.java # 전체 REST API +│ │ │ ├── model/News.java +│ │ │ ├── model/Inquiry.java +│ │ │ ├── service/NewsService.java +│ │ │ ├── service/InquiryService.java +│ │ │ ├── repository/NewsRepository.java +│ │ │ ├── repository/InquiryRepository.java +│ │ │ └── config/DataInitializer.java # 초기 뉴스 데이터 +│ │ └── resources/ +│ │ └── application.yml # SQLite 설정 +│ └── data/zioinfo.db # SQLite DB 파일 (자동 생성) +│ +└── frontend/ # React SPA (포트 3000, 개발) + ├── public/ + │ ├── logo.png # 지오정보기술 로고 + │ ├── logo-white.png # 흰색 로고 (푸터) + │ ├── favicon.ico + │ └── screenshots/ # GUARDiA 실행 화면 캡처 6장 + ├── src/ + │ ├── components/layout/ + │ │ ├── Header.jsx + .css # 고정 헤더 + 드롭다운 메뉴 + │ │ └── Footer.jsx + .css # 회사 정보 + 링크 + │ ├── pages/ + │ │ ├── Home.jsx + .css # 메인 (히어로 슬라이더) + │ │ ├── GuardiaDetail.jsx + .css # GUARDiA 솔루션 상세 + │ │ ├── Contact.jsx + .css # 문의하기 폼 + │ │ ├── Company.jsx # 회사소개 + │ │ ├── NewsPage.jsx # 뉴스룸 + │ │ └── Recruit.jsx # 채용 + │ ├── styles/global.css # 디자인 시스템 (변수/공통) + │ ├── App.jsx # 라우팅 + │ └── main.jsx # 진입점 + ├── package.json + └── vite.config.js # Spring Boot 백엔드 프록시 +``` + +--- + +## 2. 실행 방법 + +### 개발 환경 (핫 리로드) + +```bash +# 터미널 1: Spring Boot 백엔드 +cd workspace/zioinfo-web/backend +./mvnw spring-boot:run + +# 터미널 2: React 프론트엔드 +cd workspace/zioinfo-web/frontend +npm install +npm run dev # http://localhost:3000 +``` + +### 운영 빌드 (단일 JAR) + +```bash +# React 빌드 → Spring Boot static 폴더로 출력 +cd frontend && npm run build + +# Spring Boot 단일 JAR 빌드 +cd ../backend && ./mvnw clean package -DskipTests + +# 실행 +java -jar target/zioinfo-web-1.0.0.jar +# → http://localhost:8080 +``` + +--- + +## 3. API 엔드포인트 + +| Method | URL | 설명 | +|--------|-----|------| +| GET | `/api/company` | 회사 정보 | +| GET | `/api/history` | 회사 연혁 | +| GET | `/api/solutions/guardia` | GUARDiA 솔루션 정보 | +| GET | `/api/menu` | 메뉴 구조 | +| GET | `/api/news` | 소식 목록 (페이지) | +| GET | `/api/news/{id}` | 소식 상세 | +| POST | `/api/inquiry` | 문의 접수 | + +--- + +## 4. DB 설정 (SQLite) + +```yaml +# application.yml +spring: + datasource: + url: jdbc:sqlite:./data/zioinfo.db + driver-class-name: org.sqlite.JDBC + jpa: + database-platform: org.hibernate.community.dialect.SQLiteDialect + hibernate.ddl-auto: update +``` + +**DB 파일 위치:** `backend/data/zioinfo.db` +**MySQL 전환 시:** `application-prod.yml`에 MySQL 설정 후 `--spring.profiles.active=prod` + +--- + +## 5. 메뉴 구성 + +| 1단계 | 2단계 서브메뉴 | +|-------|--------------| +| 회사소개 | CEO 인사말, 연혁, 조직도, CI 소개, 오시는 길 | +| 솔루션 | **GUARDiA ITSM** ★, ERP, CRM, BI | +| 사업실적 | 구축 레퍼런스, 파트너 | +| 고객지원 | 공지사항, FAQ, 카탈로그, 문의하기 | +| 채용 | 채용공고, 복리후생, 지원하기 | +| 뉴스 | 뉴스룸, 기술 블로그 | + +--- + +## 6. GUARDiA 솔루션 소개 페이지 (`/solution/guardia`) + +**탭 구성:** +1. **핵심 기능** — 실행 스크린샷 6장 + 8가지 기능 카드 +2. **Messenger Bot** — 25개 명령어 카탈로그 + 운영 시나리오 데모 +3. **에디션 비교** — COMMUNITY / STANDARD / ENTERPRISE +4. **기술 스택** — Backend/AI/Infra/Frontend/DevOps/모니터링 +5. **도입 사례** — 광역기관/공공IT센터/교육청 + +--- + +## 7. 디자인 시스템 + +```css +/* 브랜드 컬러 */ +--primary: #0051A2; /* 딥블루 */ +--accent: #00A3E0; /* 포인트 */ +--secondary: #1A1A2E; /* 다크 네이비 */ + +/* 폰트 */ +--font-sans: 'Noto Sans KR', 'Inter', sans-serif; +``` + +**참조 디자인:** https://www.urpai.co.kr/ + +--- + +## 8. 스크린샷 추가/갱신 + +GUARDiA 화면이 변경될 때 재캡처: + +```bash +cd workspace/zioinfo-web +python3 capture_screenshots.py # (추가 예정) +``` + +현재 캡처된 화면: +- `01_dashboard.png` — 통합 대시보드 +- `02_sr_list.png` — SR 서비스 요청 +- `03_si_project.png` — PMS 프로젝트 +- `04_incidents.png` — 인시던트 관리 +- `05_agents.png` — AI 에이전트 +- `06_license.png` — 라이선스 관리 + +--- + +## 9. TODO (다음 단계) + +- [ ] 관리자 페이지 (`/admin`) — 뉴스 CRUD, 문의 답변 +- [ ] 채용 지원 폼 처리 +- [ ] 오시는 길 지도 (카카오맵 연동) +- [ ] SEO 메타태그 완성 +- [ ] 다크모드 대응 +- [ ] E2E 테스트 (Playwright) +- [ ] Jenkins CI/CD 연동 (GUARDiA 배포 파이프라인) diff --git a/workspace/zioinfo-web/backend/pom.xml b/workspace/zioinfo-web/backend/pom.xml new file mode 100644 index 00000000..f7954cb9 --- /dev/null +++ b/workspace/zioinfo-web/backend/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + + kr.co.zioinfo + zioinfo-web + 1.0.0 + jar + ZioInfo Web + (주)지오정보기술 기업 홈페이지 + + + 17 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.xerial + sqlite-jdbc + 3.45.3.0 + + + + org.hibernate.orm + hibernate-community-dialects + + + + + com.mysql + mysql-connector-j + runtime + true + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/ZioinfoWebApplication.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/ZioinfoWebApplication.java new file mode 100644 index 00000000..a171330a --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/ZioinfoWebApplication.java @@ -0,0 +1,13 @@ +package kr.co.zioinfo.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@SpringBootApplication +@EnableJpaAuditing +public class ZioinfoWebApplication { + public static void main(String[] args) { + SpringApplication.run(ZioinfoWebApplication.class, args); + } +} diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/config/DataInitializer.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/config/DataInitializer.java new file mode 100644 index 00000000..18d17f45 --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/config/DataInitializer.java @@ -0,0 +1,58 @@ +package kr.co.zioinfo.web.config; + +import kr.co.zioinfo.web.model.News; +import kr.co.zioinfo.web.repository.NewsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class DataInitializer implements CommandLineRunner { + + private final NewsRepository newsRepo; + + @Override + public void run(String... args) { + if (newsRepo.count() > 0) return; + + newsRepo.save(News.builder() + .title("GUARDiA ITSM 2.0 정식 출시 — AI 기반 인프라 자율 운영 플랫폼") + .category("보도자료") + .summary("(주)지오정보기술이 공공기관 레거시 인프라 자동화를 위한 GUARDiA ITSM 2.0을 정식 출시했습니다.") + .content("GUARDiA ITSM 2.0은 메신저 한 줄 명령으로 에이전트리스 SSH/SFTP 배포·운영을 자동화하는 온프레미스 플랫폼입니다. " + + "1,000개 이상 관공서를 대상으로 하며 외부 클라우드 의존 없이 완전 폐쇄망 환경에서 동작합니다.") + .visible(true).viewCount(128).build()); + + newsRepo.save(News.builder() + .title("2026 공공기관 AI 인프라 혁신 박람회 참가") + .category("공지사항") + .summary("지오정보기술이 2026 공공기관 AI 인프라 혁신 박람회에 참가하여 GUARDiA 솔루션을 선보입니다.") + .content("박람회 기간: 2026년 6월 15일~17일 / 장소: 코엑스 A홀 / 부스: A-215\n" + + "GUARDiA ITSM 라이브 데모 및 도입 상담을 진행합니다.") + .visible(true).viewCount(87).build()); + + newsRepo.save(News.builder() + .title("행정안전부 공공SW 우수제품 선정") + .category("보도자료") + .summary("GUARDiA ITSM이 행정안전부 2026년 공공SW 우수제품으로 선정되었습니다.") + .content("행정안전부는 공공기관 정보화 사업에 적합한 소프트웨어를 심사하여 우수제품을 선정합니다. " + + "GUARDiA는 보안성, 안정성, 공공 적합성에서 높은 평가를 받았습니다.") + .visible(true).viewCount(214).build()); + + newsRepo.save(News.builder() + .title("Spring 2026 개발자 컨퍼런스 기술 발표") + .category("이벤트") + .summary("지오정보기술 개발팀이 AI 에이전트 기반 ChatOps 구현 사례를 발표합니다.") + .content("발표 주제: '1000개 관공서 인프라를 메신저 봇 하나로 관리하기'\n" + + "일시: 2026년 5월 20일 / 장소: 서울 COEX 컨퍼런스룸") + .visible(true).viewCount(63).build()); + + newsRepo.save(News.builder() + .title("특허 등록 — 에이전트리스 레거시 인프라 자동화 방법") + .category("공지사항") + .summary("에이전트 설치 없이 SSH/SFTP 프로토콜만으로 레거시 WAS를 자동화하는 방법에 대한 특허가 등록되었습니다.") + .content("특허명: 에이전트리스 레거시 인프라 자동화 시스템 및 방법\n등록번호: 10-2026-XXXXXXX") + .visible(true).viewCount(41).build()); + } +} diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/controller/ApiController.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/controller/ApiController.java new file mode 100644 index 00000000..7bbd6b1e --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/controller/ApiController.java @@ -0,0 +1,183 @@ +package kr.co.zioinfo.web.controller; + +import kr.co.zioinfo.web.model.Inquiry; +import kr.co.zioinfo.web.model.News; +import kr.co.zioinfo.web.service.InquiryService; +import kr.co.zioinfo.web.service.NewsService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.*; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import jakarta.validation.Valid; +import java.util.*; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +@CrossOrigin(origins = {"http://localhost:3000", "http://localhost:5173"}) +public class ApiController { + + private final NewsService newsService; + private final InquiryService inquiryService; + + // ── 회사 정보 ──────────────────────────────────────────────── + @GetMapping("/company") + public ResponseEntity> getCompanyInfo() { + Map info = new LinkedHashMap<>(); + info.put("name", "(주)지오정보기술"); + info.put("ceo", "대표이사"); + info.put("founded", "2000년"); + info.put("address", "서울특별시"); + info.put("phone", "02-000-0000"); + info.put("email", "info@zioinfo.co.kr"); + info.put("business", Arrays.asList( + Map.of("name", "GUARDiA ITSM", "desc", "AI 기반 레거시 인프라 자율 운영 플랫폼"), + Map.of("name", "ERP 솔루션", "desc", "기업 자원관리 시스템"), + Map.of("name", "SI 구축", "desc", "정보화사업 시스템 통합"), + Map.of("name", "IT 인프라", "desc", "인프라 구축 및 운영") + )); + return ResponseEntity.ok(info); + } + + // ── 연혁 ───────────────────────────────────────────────────── + @GetMapping("/history") + public ResponseEntity>> getHistory() { + List> history = new ArrayList<>(); + history.add(Map.of("year", "2026", "events", List.of( + "GUARDiA ITSM v2.0 출시 (AI 자율 운영 플랫폼)", + "공공기관 AI 인프라 자동화 사업 수주" + ))); + history.add(Map.of("year", "2024", "events", List.of( + "GUARDiA ITSM v1.0 개발 완료", + "관공서 레거시 인프라 자동화 특허 출원" + ))); + history.add(Map.of("year", "2022", "events", List.of( + "AI 기반 ChatOps 플랫폼 연구 개발 착수", + "행정기관 SI 사업 10건 수주" + ))); + history.add(Map.of("year", "2020", "events", List.of( + "창립 20주년", + "클라우드 전환 컨설팅 사업 진출" + ))); + history.add(Map.of("year", "2010", "events", List.of( + "ERP·CRM 솔루션 공급 100개사 달성" + ))); + history.add(Map.of("year", "2000", "events", List.of( + "(주)지오정보기술 설립" + ))); + return ResponseEntity.ok(history); + } + + // ── GUARDiA 솔루션 정보 ─────────────────────────────────────── + @GetMapping("/solutions/guardia") + public ResponseEntity> getGuardiaInfo() { + Map g = new LinkedHashMap<>(); + g.put("name", "GUARDiA ITSM"); + g.put("tagline", "AI 기반 레거시 인프라 자율 운영 플랫폼"); + g.put("description", + "1,000개 이상 관공서 레거시 인프라를 대상으로 하는 AI 기반 통합 ChatOps 오케스트레이션 플랫폼. " + + "메신저 한 줄 명령으로 에이전트리스 배포·운영을 자동화합니다."); + g.put("keyFeatures", Arrays.asList( + Map.of("icon", "🤖", "title", "AI 에이전트 자동화", + "desc", "Ollama 온프레미스 sLLM 기반 자연어 명령 → 자동 배포·운영"), + Map.of("icon", "🔧", "title", "에이전트리스 아키텍처", + "desc", "대상 서버에 소프트웨어 설치 없이 SSH/SFTP만으로 관리"), + Map.of("icon", "💬", "title", "ChatOps 메신저 통합", + "desc", "카카오워크·네이버웍스·슬랙 등 메신저에서 직접 인프라 제어"), + Map.of("icon", "📊", "title", "통합 ITSM 대시보드", + "desc", "SR·인시던트·변경관리·SLA·CMDB 전체를 하나의 플랫폼에서"), + Map.of("icon", "🔒", "title", "엔터프라이즈 보안", + "desc", "AES-256-GCM 암호화, MFA, PAM, 불변 감사로그, Zero Trust"), + Map.of("icon", "🏗️", "title", "PMS (프로젝트 관리)", + "desc", "WBS·산출물·일주월 보고서 자동 생성, 이슈·위험 관리") + )); + g.put("editions", Arrays.asList( + Map.of("name", "COMMUNITY", "price", "무료", "target", "소규모 기관", + "features", List.of("기본 SR 관리", "CMDB", "대시보드")), + Map.of("name", "STANDARD", "price", "협의", "target", "중형 기관", + "features", List.of("전체 ITSM", "AI 에이전트", "LDAP/MFA", "SLA")), + Map.of("name", "ENTERPRISE", "price", "협의", "target", "대형 관공서", + "features", List.of("무제한", "취약점 스캔", "Scouter APM", "FinOps", "전담 지원")) + )); + g.put("techStack", Map.of( + "backend", "Python 3.11 / FastAPI", + "ai", "Ollama (온프레미스 sLLM, 외부 API 완전 금지)", + "infra", "paramiko SSH/SFTP (에이전트리스)", + "db", "PostgreSQL / SQLite", + "frontend", "React.js / PWA" + )); + return ResponseEntity.ok(g); + } + + // ── 소식 목록 ──────────────────────────────────────────────── + @GetMapping("/news") + public ResponseEntity> getNews( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "6") int size, + @RequestParam(required = false) String category) { + Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); + return ResponseEntity.ok(newsService.findAll(category, pageable)); + } + + @GetMapping("/news/{id}") + public ResponseEntity getNewsDetail(@PathVariable Long id) { + return newsService.findById(id) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } + + // ── 문의 접수 ──────────────────────────────────────────────── + @PostMapping("/inquiry") + public ResponseEntity> submitInquiry(@Valid @RequestBody Inquiry inquiry) { + inquiryService.save(inquiry); + return ResponseEntity.ok(Map.of( + "message", "문의가 접수되었습니다. 빠른 시일 내에 연락드리겠습니다.", + "status", "SUCCESS" + )); + } + + // ── 메뉴 구조 ──────────────────────────────────────────────── + @GetMapping("/menu") + public ResponseEntity>> getMenu() { + return ResponseEntity.ok(List.of( + Map.of("id", "company", "label", "회사소개", + "children", List.of( + Map.of("id", "greeting", "label", "CEO 인사말", "path", "/company/greeting"), + Map.of("id", "history", "label", "연혁", "path", "/company/history"), + Map.of("id", "organization", "label", "조직도", "path", "/company/organization"), + Map.of("id", "ci", "label", "CI 소개", "path", "/company/ci"), + Map.of("id", "location", "label", "오시는 길", "path", "/company/location") + )), + Map.of("id", "solution", "label", "솔루션", + "children", List.of( + Map.of("id", "guardia", "label", "GUARDiA ITSM", "path", "/solution/guardia", "badge", "NEW"), + Map.of("id", "erp", "label", "ERP", "path", "/solution/erp"), + Map.of("id", "crm", "label", "CRM", "path", "/solution/crm"), + Map.of("id", "bi", "label", "BI", "path", "/solution/bi") + )), + Map.of("id", "business", "label", "사업실적", + "children", List.of( + Map.of("id", "reference", "label", "구축 레퍼런스", "path", "/business/reference"), + Map.of("id", "partner", "label", "파트너", "path", "/business/partner") + )), + Map.of("id", "support", "label", "고객지원", + "children", List.of( + Map.of("id", "notice", "label", "공지사항", "path", "/support/notice"), + Map.of("id", "faq", "label", "FAQ", "path", "/support/faq"), + Map.of("id", "catalog", "label", "카탈로그", "path", "/support/catalog"), + Map.of("id", "contact", "label", "문의하기", "path", "/support/contact") + )), + Map.of("id", "recruit", "label", "채용", + "children", List.of( + Map.of("id", "jobs", "label", "채용공고", "path", "/recruit/jobs"), + Map.of("id", "welfare", "label", "복리후생", "path", "/recruit/welfare"), + Map.of("id", "apply", "label", "지원하기", "path", "/recruit/apply") + )), + Map.of("id", "news", "label", "뉴스", + "children", List.of( + Map.of("id", "newsroom", "label", "뉴스룸", "path", "/news/newsroom"), + Map.of("id", "blog", "label", "기술 블로그", "path", "/news/blog") + )) + )); + } +} diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/model/Inquiry.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/model/Inquiry.java new file mode 100644 index 00000000..7be8f5af --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/model/Inquiry.java @@ -0,0 +1,40 @@ +package kr.co.zioinfo.web.model; + +import jakarta.persistence.*; +import jakarta.validation.constraints.*; +import lombok.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.time.LocalDateTime; + +@Entity @Table(name = "inquiry") +@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class Inquiry { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotBlank @Column(nullable = false, length = 50) + private String name; + + @NotBlank @Email @Column(nullable = false, length = 100) + private String email; + + @Column(length = 30) + private String phone; + + @NotBlank @Column(nullable = false, length = 200) + private String subject; + + @NotBlank @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @Column(length = 50) + private String category; // 제품문의 | 기술지원 | 사업제안 | 기타 + + private boolean agreePrivacy = false; + private String status = "PENDING"; // PENDING | ANSWERED + + @CreatedDate + private LocalDateTime createdAt; +} diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/model/News.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/model/News.java new file mode 100644 index 00000000..99358db5 --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/model/News.java @@ -0,0 +1,36 @@ +package kr.co.zioinfo.web.model; + +import jakarta.persistence.*; +import lombok.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.time.LocalDateTime; + +@Entity @Table(name = "news") +@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class News { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 200) + private String title; + + @Column(length = 50) + private String category; // 공지사항 | 보도자료 | 이벤트 + + @Column(columnDefinition = "TEXT") + private String content; + + @Column(length = 500) + private String summary; + + @Column(length = 300) + private String thumbnailUrl; + + private boolean visible = true; + private int viewCount = 0; + + @CreatedDate + private LocalDateTime createdAt; +} diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/repository/InquiryRepository.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/repository/InquiryRepository.java new file mode 100644 index 00000000..44e7e5c4 --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/repository/InquiryRepository.java @@ -0,0 +1,4 @@ +package kr.co.zioinfo.web.repository; +import kr.co.zioinfo.web.model.Inquiry; +import org.springframework.data.jpa.repository.JpaRepository; +public interface InquiryRepository extends JpaRepository {} diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/repository/NewsRepository.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/repository/NewsRepository.java new file mode 100644 index 00000000..9a651c5e --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/repository/NewsRepository.java @@ -0,0 +1,8 @@ +package kr.co.zioinfo.web.repository; +import kr.co.zioinfo.web.model.News; +import org.springframework.data.domain.*; +import org.springframework.data.jpa.repository.JpaRepository; +public interface NewsRepository extends JpaRepository { + Page findByVisibleTrue(Pageable p); + Page findByCategoryAndVisibleTrue(String category, Pageable p); +} diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/service/InquiryService.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/service/InquiryService.java new file mode 100644 index 00000000..95f759b8 --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/service/InquiryService.java @@ -0,0 +1,21 @@ +package kr.co.zioinfo.web.service; + +import kr.co.zioinfo.web.model.Inquiry; +import kr.co.zioinfo.web.repository.InquiryRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class InquiryService { + private final InquiryRepository repo; + + public Inquiry save(Inquiry inquiry) { + Inquiry saved = repo.save(inquiry); + log.info("문의 접수: id={} name={} subject={}", saved.getId(), saved.getName(), saved.getSubject()); + // TODO: 이메일 발송 (MailService 연동) + return saved; + } +} diff --git a/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/service/NewsService.java b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/service/NewsService.java new file mode 100644 index 00000000..de876d32 --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/java/kr/co/zioinfo/web/service/NewsService.java @@ -0,0 +1,28 @@ +package kr.co.zioinfo.web.service; + +import kr.co.zioinfo.web.model.News; +import kr.co.zioinfo.web.repository.NewsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class NewsService { + private final NewsRepository repo; + + public Page findAll(String category, Pageable pageable) { + if (category != null && !category.isBlank()) { + return repo.findByCategoryAndVisibleTrue(category, pageable); + } + return repo.findByVisibleTrue(pageable); + } + + public Optional findById(Long id) { + return repo.findById(id).map(n -> { + n.setViewCount(n.getViewCount() + 1); + return repo.save(n); + }); + } +} diff --git a/workspace/zioinfo-web/backend/src/main/resources/application.yml b/workspace/zioinfo-web/backend/src/main/resources/application.yml new file mode 100644 index 00000000..2187e8f5 --- /dev/null +++ b/workspace/zioinfo-web/backend/src/main/resources/application.yml @@ -0,0 +1,49 @@ +server: + port: 8080 + servlet: + encoding: + charset: UTF-8 + force: true + +spring: + application: + name: zioinfo-web + datasource: + # SQLite — 파일 기반 DB (data/ 디렉토리 자동 생성) + url: jdbc:sqlite:./data/zioinfo.db + driver-class-name: org.sqlite.JDBC + jpa: + database-platform: org.hibernate.community.dialect.SQLiteDialect + hibernate: + ddl-auto: update # 스키마 자동 갱신 (운영: validate) + show-sql: false + properties: + hibernate: + format_sql: true + # SQLite는 foreign key 비활성 기본 → 명시 활성화 + javax.persistence.schema-generation.database.action: none + mail: + host: ${MAIL_HOST:smtp.gmail.com} + port: ${MAIL_PORT:587} + username: ${MAIL_USERNAME:} + password: ${MAIL_PASSWORD:} + properties: + mail.smtp.auth: true + mail.smtp.starttls.enable: true + +zioinfo: + company: + name: (주)지오정보기술 + email: info@zioinfo.co.kr + phone: 02-000-0000 + address: 서울특별시 + cors: + allowed-origins: + - http://localhost:3000 + - http://localhost:5173 + - http://www.zioinfo.co.kr + +logging: + level: + kr.co.zioinfo: DEBUG + org.hibernate.SQL: WARN diff --git a/workspace/zioinfo-web/frontend/package.json b/workspace/zioinfo-web/frontend/package.json new file mode 100644 index 00000000..07a25ea7 --- /dev/null +++ b/workspace/zioinfo-web/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "zioinfo-web-frontend", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.23.1", + "axios": "^1.7.2", + "swiper": "^11.1.4", + "react-intersection-observer": "^9.10.3", + "react-countup": "^6.5.3", + "react-scroll": "^1.9.0" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.0", + "vite": "^5.2.11" + } +} diff --git a/workspace/zioinfo-web/frontend/public/favicon.ico b/workspace/zioinfo-web/frontend/public/favicon.ico new file mode 100644 index 00000000..3252b9f8 Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/favicon.ico differ diff --git a/workspace/zioinfo-web/frontend/public/index.html b/workspace/zioinfo-web/frontend/public/index.html new file mode 100644 index 00000000..fe630a0e --- /dev/null +++ b/workspace/zioinfo-web/frontend/public/index.html @@ -0,0 +1,18 @@ + + + + + + + + (주)지오정보기술 + + + + + + +
+ + + diff --git a/workspace/zioinfo-web/frontend/public/logo-white.png b/workspace/zioinfo-web/frontend/public/logo-white.png new file mode 100644 index 00000000..0ae65e05 Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/logo-white.png differ diff --git a/workspace/zioinfo-web/frontend/public/logo.png b/workspace/zioinfo-web/frontend/public/logo.png new file mode 100644 index 00000000..4f88262a Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/logo.png differ diff --git a/workspace/zioinfo-web/frontend/public/screenshots/01_dashboard.png b/workspace/zioinfo-web/frontend/public/screenshots/01_dashboard.png new file mode 100644 index 00000000..6728dfd8 Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/screenshots/01_dashboard.png differ diff --git a/workspace/zioinfo-web/frontend/public/screenshots/02_sr_list.png b/workspace/zioinfo-web/frontend/public/screenshots/02_sr_list.png new file mode 100644 index 00000000..6728dfd8 Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/screenshots/02_sr_list.png differ diff --git a/workspace/zioinfo-web/frontend/public/screenshots/03_si_project.png b/workspace/zioinfo-web/frontend/public/screenshots/03_si_project.png new file mode 100644 index 00000000..6481c7dc Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/screenshots/03_si_project.png differ diff --git a/workspace/zioinfo-web/frontend/public/screenshots/04_incidents.png b/workspace/zioinfo-web/frontend/public/screenshots/04_incidents.png new file mode 100644 index 00000000..a50691cb Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/screenshots/04_incidents.png differ diff --git a/workspace/zioinfo-web/frontend/public/screenshots/05_agents.png b/workspace/zioinfo-web/frontend/public/screenshots/05_agents.png new file mode 100644 index 00000000..847ccb8f Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/screenshots/05_agents.png differ diff --git a/workspace/zioinfo-web/frontend/public/screenshots/06_license.png b/workspace/zioinfo-web/frontend/public/screenshots/06_license.png new file mode 100644 index 00000000..52f91192 Binary files /dev/null and b/workspace/zioinfo-web/frontend/public/screenshots/06_license.png differ diff --git a/workspace/zioinfo-web/frontend/src/App.jsx b/workspace/zioinfo-web/frontend/src/App.jsx new file mode 100644 index 00000000..ae8f8227 --- /dev/null +++ b/workspace/zioinfo-web/frontend/src/App.jsx @@ -0,0 +1,42 @@ +import React, { Suspense, lazy } from 'react'; +import { Routes, Route, useLocation } from 'react-router-dom'; +import Header from './components/layout/Header'; +import Footer from './components/layout/Footer'; + +const Home = lazy(() => import('./pages/Home')); +const GuardiaDetail = lazy(() => import('./pages/GuardiaDetail')); +const Company = lazy(() => import('./pages/Company')); +const Contact = lazy(() => import('./pages/Contact')); +const NewsPage = lazy(() => import('./pages/NewsPage')); +const Recruit = lazy(() => import('./pages/Recruit')); +const NotFound = lazy(() => import('./pages/NotFound')); + +function Loading() { + return ( +
+ 로딩 중... +
+ ); +} + +export default function App() { + const location = useLocation(); + return ( + <> +
+ }> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + +