Compare commits

...

28 Commits

Author SHA1 Message Date
Arin(asus)
a5d6309c38 fix 광주동구검색기 2025-08-14 14:47:20 +09:00
Arin(asus)
02fe9c4eb9 feat: 도서관 검색기 최적화 및 브라우저 헤더 통일화
- 경남대표도서관 검색기를 직접 URL 네비게이션 방식으로 수정하여 JavaScript 변수 문제 해결
- HttpApiMode가 true인 모든 검색기에 표준 브라우저 헤더 적용 (목포, 여수, 광산, 동구, 익산, 완도)
- Selenium 기반 검색기의 80% 브라우저 배율 설정 유지

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 14:46:00 +09:00
Arin(asus)
44f6c748e5 fix: 안산시 도서관 검색기 대폭 개선
- 도서관 선택 로직 완전 재작성 (체크박스 상태 정확히 확인)
- 검색어 입력 개선 (ElementNotInteractableException 해결)
  * 요소 상호작용 가능성 확인 및 스크롤
  * readonly 속성 제거 및 포커스 설정
  * JavaScript 대체 입력 방법 추가
- 검색 실행 4단계 백업 방법 구현
  * JavaScript Enter 이벤트 발생
  * 폼 직접 제출
  * JavaScript 버튼 클릭
  * URL 직접 이동
- 검색 결과 추출 안산시 특화 (span.count.gothic em)
- 상세한 디버깅 로그 추가로 문제 진단 개선

안산시 도서관 사이트의 특수한 제약사항 모두 우회

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 12:17:11 +09:00
Arin(asus)
946957bab2 feat: 도서관 검색 시스템 대폭 확장 및 크롤링 가이드 추가
- 새로운 도서관 검색기 15개 추가 (HTTP/Selenium 방식)
  * HTTP 방식: 순천시립, 목포시립, 광산구, 여수시립
  * Selenium 방식: 광주시립, 고흥군립, 북구통합, 전북교육청, 안산시립 등
- 도서관 검색기 작성 가이드를 CLAUDE.md에 추가
- ILibrarySearcher 인터페이스에 HttpApiMode 속성 추가
- 기존 검색기들 리팩토링 및 통합 (NamguLibrarySearcher 등)
- Check_copyWD.cs에 모든 새로운 도서관 등록 완료
- 설정 관리 시스템 개선 (UserSetting 클래스 추가)

총 200개 이상의 도서관 지원으로 복본조사 범위 대폭 확대

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-14 01:21:33 +09:00
f3715253a8 .. 2025-08-13 18:39:23 +09:00
Arin(asus)
0c190e112f feat: DLS 복본조사 개선 및 개발자용 DB 편집 기능 추가
- 전남교육청 도서관 검색 기능 추가 (JunnamEduSearcher)
- 개발자용 데이터베이스 편집 도구 추가 (디버그 모드에서만 표시)
- UI 개선: 브라우저 표시 옵션, 검색 결과 표시 개선
- Helper_DB에 CreateConnection 메서드 추가
- URL 처리 개선 (따옴표 자동 추가)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-13 15:22:50 +09:00
SeungHo Yang
e7c7bc2b36 dls 복본조사
1.납품처 입력칸 기본 한글로
  2.납품처 선택하면 자동 접속
  3.복본조사결과를 Y/N에서 권수로 변경

납품처 정보 변경 후 발생되는 데이터베이스 오류 수정
퀵메뉴 관련 오류 수정
2025-08-13 12:06:09 +09:00
SeungHo Yang
90d8757270 DLS복본검사기능 데모 판 완료. 2025-08-13 01:03:19 +09:00
e206c96d72 revert 99cba2626a
revert clean: 불필요한 빌드 및 패키지 파일 정리

개발 환경의 빌드 산출물과 패키지 캐시 파일들을 저장소에서 제거하여
저장소 크기를 최적화하고 버전 관리에서 제외

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-12 13:29:50 +00:00
3908bc92ed Merge branch 'chi202506' of https://git.tindevil.com/Gloria/Unimarc into chi202506
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2025-08-12 22:26:00 +09:00
99cba2626a clean: 불필요한 빌드 및 패키지 파일 정리
개발 환경의 빌드 산출물과 패키지 캐시 파일들을 저장소에서 제거하여
저장소 크기를 최적화하고 버전 관리에서 제외

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-12 22:23:59 +09:00
76fb7c9ce8 Delete ISBN_Check_test/.vs/ISBN_Check_test/v16/.suo 2025-08-12 13:18:25 +00:00
gloriasub
2f15de4139 .. 2025-08-12 22:15:28 +09:00
gloriasub
ab2ac6e78c 파일정리 2025-08-12 21:59:19 +09:00
0f3d985b82 refactor: SearchModel 구조 개선 및 비동기 처리 최적화
- ChromeDriverManager.cs 제거하여 코드 중복 제거
- ILibrarySearcher 인터페이스의 StartDriver 메서드를 async로 변경
- KwangjuCityLibrarySearcher 및 NamguLibrarySearcher에 ChromeDriverHelper 적용
- 드라이버 생성 로직을 통합하여 일관성 있는 구조로 개선
- Check_copyWD.cs 및 DLS_Copy.cs에서 비동기 검색 처리 개선

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-12 19:36:53 +09:00
4d1450d2c5 feat: 브라우저 설치 확인 및 드라이버 생성 개선
- 브라우저 설치 여부 확인 메서드 추가 (Chrome, Edge)
- TestDriver 메서드를 우선순위 기반 테스트로 개선 (Edge > Chrome)
- 드라이버 콘솔창 숨김 기능 추가 (HideCommandPromptWindow)
- 웹드라이버 감지 방지 스크립트 안전성 개선
- 관리자 권한 없이도 브라우저 설치 확인 가능

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-12 19:32:37 +09:00
Arin(asus)
c3a309092e fix: MySQL 패키지 업데이트 및 코드 최적화
- MySql.Data를 9.4.0으로 업데이트하여 호환성 개선
- 불필요한 MySqlX.XDevAPI.Relational using 구문 제거
- 컴파일러 경고 억제를 위한 NoWarn 설정 추가
- SeleniumHelper.cs 파일 추가
- readonly 키워드 적용으로 코드 품질 향상

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-12 17:21:19 +09:00
gloriasub
5464563aea (ing) webdriver init error 2025-08-12 00:54:16 +09:00
7ca6712e29 refactor: Chrome 옵션 중복 제거 및 코드 구조 개선 2025-08-12 00:18:04 +09:00
2f1c2483f0 Revert "WebView2 Fixed Version 호환성 문제 해결 - NuGet 패키지 버전 업데이트 및 환경 설정 개선"
This reverts commit a13306115b.
2025-08-11 21:42:35 +09:00
gloriasub
3d75d1192d remove binary file 2025-08-11 16:19:50 +09:00
a13306115b WebView2 Fixed Version 호환성 문제 해결 - NuGet 패키지 버전 업데이트 및 환경 설정 개선 2025-08-11 14:42:57 +09:00
SeungHo Yang
5949e3e7a1 프로그램 아이콘 적용.
프로그램 시작시 clientexe 교체 오류로 인해, 바로 종료되는 현상 수정.
복본조사 화면을  old 와 new 로 분리 함.
2025-07-28 22:26:57 +09:00
SeungHo Yang
6cd5ab6f1c 복본검사 화면 분리. - 등록된 부서의 사용자만 보이도록 함. 2025-07-27 21:05:54 +09:00
SeungHo Yang
9be6834d9f 웹페이지가 화면요소가 없는 경웁checkbox 클릭오류 수정 2025-07-09 23:25:42 +09:00
SeungHo Yang
12d6c2dbfb 검색결과 수량부분이 자세히 표시됨
오류 메세지 표현 방식 변경
2025-07-09 22:52:54 +09:00
SeungHo Yang
8b63566684 2025-07-07 22:00~23:00
* 복본검사
	검색대상(도서관) : 기본입력을 한글로
	검색시 검색윈도우 최소화 기능 일시 중지
	검색옵션추가 (전체, 오류+없음, 오류, 없음)
2025-07-07 23:28:22 +09:00
SeungHo Yang
b7c6e530dd 2025-07-06 19:30~ 21:35, 10:30 ~
Chrome Web Driver 추가(신규 복본검사 로직용)
도서관 검색버튼 추가
도서관 선택없이 검색시작을 누르면 오류 표시
도서관 DB 정보에 신규로직용 식별 키(SearcherNo:int) 추가
  - 광주광역시 남구 (문화, 청소년, 스마트, 푸른길)
2025-07-06 23:49:08 +09:00
244 changed files with 11060 additions and 53445 deletions

View File

@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"WebFetch(domain:jnelib.jne.go.kr)"
],
"deny": [],
"ask": []
}
}

9
.gitignore vendored
View File

@@ -1,4 +1,6 @@
## 파일무시
desktop.ini
UpgradeLog.htm
## 다음과 같은 확장자는 전체 무시
.vs
@@ -14,6 +16,7 @@ packages
*.xlsx
*.zip
## 폴더 무시
unimarc/Factory_Client/
ISBN_Client/
@@ -31,6 +34,6 @@ Test_DLS/
MarcCroling/
OutPutMarc/
GolfTicketing/
/unimarc/UniMarc/obj
/unimarc/UniMarc/.vs
/unimarc/UniMarc/bin
*.msi
/unimarc/PatchList/~$유니막 수정내역2.pptx

View File

@@ -0,0 +1,26 @@
{
"hooks": {
"conversation-start": "새로운 대화를 시작할 때 항상 CLAUDE.md 파일을 자동으로 읽고 프로젝트 컨텍스트를 파악하세요",
"user-prompt-submit": "작업을 시작하기 전에 항상 CLAUDE.md 파일을 읽고 참조하세요"
},
"rules": [
"대화 시작 시 반드시 CLAUDE.md 파일을 읽어서 프로젝트 컨텍스트를 파악할 것",
"모든 답변은 한국어로 작성할 것",
"UniMarc 프로젝트는 C# WinForms 기반의 도서관 관리 시스템임"
],
"context_files": [
"CLAUDE.md"
],
"project_info": {
"name": "UniMarc",
"type": "도서관 자료 관리 시스템",
"tech_stack": "C# WinForms, .NET Framework 4.7.2, MySQL"
},
"permissions": {
"allow": [
"Bash(git add:*)"
],
"deny": [],
"ask": []
}
}

View File

@@ -0,0 +1,6 @@
{
"hooks": {
"conversation-start": "새로운 대화를 시작할 때 항상 CLAUDE.md 파일을 자동으로 읽고 프로젝트 컨텍스트를 파악하세요",
"user-prompt-submit": "작업을 시작하기 전에 항상 CLAUDE.md 파일을 읽고 참조하세요"
}
}

9
unimarc/.cursorignore Normal file
View File

@@ -0,0 +1,9 @@
# 이 파일들은 항상 컨텍스트에 포함
!CLAUDE.md
!.claude/settings.local.json
# 제외할 파일들
bin/
obj/
*.dll
*.pdb

1
unimarc/.cursorrules Normal file
View File

@@ -0,0 +1 @@
CLAUDE.md 파일을 참조하여 답변하세요

156
unimarc/CLAUDE.md Normal file
View File

@@ -0,0 +1,156 @@
# ⚠️ 중요: 대화 시작시 이 파일을 반드시 읽으세요!
# 답변은 가급적이면 한글로!
# UniMarc 프로젝트 - Claude 작업 가이드
> **Claude에게**: 대화를 시작할 때마다 이 파일을 먼저 읽어서 프로젝트 컨텍스트를 파악하세요.
## 프로젝트 개요
- **프로젝트명**: UniMarc (도서관 자료 관리 시스템)
- **기술스택**: C# WinForms, .NET Framework 4.7.2
- **데이터베이스**: MySQL
- **주요기능**: 마크 작성, 복본조사, DLS 연동, 도서 정보 관리
## 코딩 컨벤션
- 파일명: PascalCase (예: DLS_Copy.cs)
- 클래스명: PascalCase
- 메서드명: PascalCase
- 변수명: camelCase
- 상수명: UPPER_CASE
## 주요 디렉토리 구조
- `/마크/`: 마크 관련 폼들
- `/납품관리/`: 납품 관리 관련 폼들
- `/마스터/`: 마스터 데이터 관리 폼들
- `/홈/`: 메인 화면 관련 폼들
- `/회계/`: 회계 관련 폼들
## 개발 시 주의사항
1. WebView2 사용 시 async/await 패턴 적용
2. 데이터베이스 연결은 Helper_DB 클래스 사용
3. 에러 처리는 try-catch 블록으로 처리
4. 한글 주석 사용
## 빌드 및 배포
- Visual Studio 2019 이상 필요
- NuGet 패키지 복원 후 빌드
- WebView2 런타임 필요
- NetFX 프로젝트이므로 dotnet 명령은 사용 불가
## MsBuild 실행파일 위치 (경로에 공백이 있으니 쌍따옴표로 감싸야 함)
## 매개변수 입력할때 platform 은 제거하고 그냥 프로젝트명만 입력
F:\(VHD) Program Files\Microsoft Visual Studio\2022\MSBuild\Current\Bin\msbuild.exe
## 프로젝트 파일명
UniMarc.csproj
## 도서관 검색기(크롤링) 클래스 작성 가이드
### 개요
도서관 웹사이트에서 도서 검색 결과를 크롤링하는 클래스들을 구현합니다.
각 도서관 사이트의 특성에 따라 HTTP 방식 또는 Selenium 방식을 선택합니다.
### 방식 선택 기준
- **HTTP 방식**: URL이 변경되는 사이트 (GET 파라미터로 검색)
- **Selenium 방식**: URL이 변경되지 않는 사이트 (POST 폼 제출)
### 1. HTTP 방식 (GET 요청)
**참고 파일**: `SuncheonLibSearcher.cs`
**특징**:
- HttpClient 사용
- URL 파라미터로 검색
- 빠른 실행 속도
- HttpApiMode = true
**주요 구현 포인트**:
```csharp
// URL 구성
var searchUrl = $"{SiteUrl}?search={encodedSearchTerm}&libCode={AreaCode}";
// HTTP 헤더 추가 (500 에러 방지)
request.Headers.Add("User-Agent", "Mozilla/5.0...");
request.Headers.Add("Accept", "text/html,application/xhtml+xml...");
// HTML에서 결과 수 정규식 추출
var patterns = new[] {
@"총\s*<strong[^>]*class=""cred""[^>]*>(\d+)</strong>\s*건"
};
```
### 2. Selenium 방식 (크롤링)
**참고 파일**: `GwangjuCityLibSearcher.cs`
**특징**:
- Selenium WebDriver 사용
- 폼 제출 방식
- 복잡한 UI 상호작용 가능
- HttpApiMode = false
**주요 구현 포인트**:
```csharp
// 도서관 선택 (드롭다운)
var libSelect = wait.Until(d => d.FindElement(By.CssSelector("select[name='libCode']")));
var selectElement = new SelectElement(libSelect);
selectElement.SelectByValue(AreaCode);
// 검색어 입력
var searchInput = wait.Until(d => d.FindElement(By.Id("bookSearchQuery")));
searchInput.Clear();
searchInput.SendKeys(searchTerm);
// 검색 버튼 클릭
var searchButton = wait.Until(d => d.FindElement(By.CssSelector("button.bookSearchBtn")));
searchButton.Click();
// 페이지 로딩 대기
await WaitForPageChange(wait);
```
### 3. 공통 인터페이스 구현
모든 검색기는 `ILibrarySearcher` 인터페이스를 구현해야 합니다:
```csharp
public interface ILibrarySearcher
{
Task<BookSearchResult> SearchAsync(string searchTerm);
Task StartDriver(bool showdriver = false);
void StopDriver();
string SiteName { get; }
string SiteUrl { get; }
bool HttpApiMode { get; set; }
int No { get; set; }
}
```
### 4. Check_copyWD.cs에 등록
새로운 검색기를 만든 후 반드시 Check_copyWD.cs의 생성자에 추가:
```csharp
// 예시: 완도군립도서관
idx = 1200;
_searchService.AddSearcher(new WandoLibSearcher(idx++, "MA", "완도군립도서관"));
_searchService.AddSearcher(new WandoLibSearcher(idx++, "MB", "노화공공도서관"));
```
### 5. 결과 추출 패턴
HTML에서 검색 결과 수를 추출하는 일반적인 패턴들:
```csharp
var patterns = new[] {
@"총\s*<strong[^>]*>(\d+)</strong>\s*건",
@"검색결과\s*총\s*(\d+)\s*건",
@"<span[^>]*class=""heighlight""[^>]*>(\d+)</span>",
@"총\s*(\d+)\s*권\(개\)"
};
```
### 6. 에러 처리
- 검색 결과 없음: `return 0`
- 추출 실패: `return -1`
- 예외 발생: BookSearchResult의 ErrorMessage에 설정
### 현재 구현된 검색기 목록
1. HTTP 방식: SuncheonLibSearcher, MokpoLibSearcher, GwangsanLibSearcher, YeosuLibSearcher
2. Selenium 방식: GwangjuCityLibSearcher, GoheungLibSearcher, BukguLibSearcher 계열, JeonbukEduLibSearcher
각 방식의 장단점을 고려하여 사이트 특성에 맞는 방식을 선택하여 구현하세요.

View File

@@ -202,7 +202,7 @@
}
"{3C67513D-01DD-4637-8A68-80971EB9504F}:_A364675CFF7847C09ACEC7EAA54B96DD"
{
"DefaultLocation" = "8:[ProgramFilesFolder][Manufacturer]\\[ProductName]"
"DefaultLocation" = "8:c:\\[Manufacturer]\\[ProductName]"
"Name" = "8:#1925"
"AlwaysCreate" = "11:FALSE"
"Condition" = "8:"

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="UniMarc.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/></startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /></startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
@@ -18,19 +18,39 @@
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://testws.baroservice.com/FAX.asmx" binding="basicHttpBinding"
bindingConfiguration="BaroService_FAXSoap" contract="BaroService_API.BaroService_FAXSoap"
name="BaroService_FAXSoap" />
<endpoint address="https://testws.baroservice.com/TI.asmx" binding="basicHttpBinding"
bindingConfiguration="BaroService_TISoap" contract="BaroService_TI.BaroService_TISoap"
name="BaroService_TISoap" />
<endpoint address="http://testws.baroservice.com/FAX.asmx" binding="basicHttpBinding" bindingConfiguration="BaroService_FAXSoap" contract="BaroService_API.BaroService_FAXSoap" name="BaroService_FAXSoap" />
<endpoint address="https://testws.baroservice.com/TI.asmx" binding="basicHttpBinding" bindingConfiguration="BaroService_TISoap" contract="BaroService_TI.BaroService_TISoap" name="BaroService_TISoap" />
</client>
</system.serviceModel>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Renci.SshNet" publicKeyToken="1cee9f8bde3db106" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2020.0.1.0" newVersion="2020.0.1.0"/>
<assemblyIdentity name="Renci.SshNet" publicKeyToken="1cee9f8bde3db106" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2025.0.0.1" newVersion="2025.0.0.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.5" newVersion="8.0.0.5" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="AngleSharp" publicKeyToken="e83494dcdc6d31ea" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.3.0.0" newVersion="1.3.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>

View File

@@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace UniMarc
{
public static class CUtill
{
public static arUtil.Log mLog;
public static void MsgI(string m)
{
//MessageWindow.VisibleAll(false);
MessageBox.Show(m, "CHECK", MessageBoxButtons.OK, MessageBoxIcon.Information);
//MessageWindow.VisibleAll(true);
}
public static void MsgE(string m)
{
//MessageWindow.VisibleAll(false);
MessageBox.Show(m, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
//MessageWindow.VisibleAll(true);
}
public static DialogResult MsgQ(string m)
{
//MessageWindow.VisibleAll(false);
DialogResult dlg = MessageBox.Show(m, "CHECK", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
//MessageWindow.VisibleAll(true);
return dlg;
}
}
}

View File

@@ -68,6 +68,34 @@ namespace WindowsFormsApp1
}
}
}
public MySql.Data.MySqlClient.MySqlConnection CreateConnection()
{
PasswordConnectionInfo connectionInfo = new PasswordConnectionInfo(ServerData[0], port, ServerData[1], ServerData[2]);
connectionInfo.Timeout = TimeSpan.FromSeconds(30);
using (var client = new SshClient(connectionInfo))
{
if (conn != null)
{
conn.Close();
conn.Dispose();
}
client.Connect();
if (client.IsConnected)
{
string strConnection = string.Format(
"Server={0};" +
"Port={1};" +
"Database=unimarc;" +
"uid={2};" +
"pwd={3};", ServerData[0], DBData[0], DBData[1], DBData[2]);
return new MySqlConnection(strConnection);
}
}
return null;
}
/// <summary>
/// 국중DB를 사용하고 싶을 때 미리 저장된 DB의 기본 접속정보를 이용하여 DB에 접근한다.
/// </summary>
@@ -194,7 +222,7 @@ namespace WindowsFormsApp1
}
public void DB_Send_CMD_reVoid(string cmd)
{
using (conn)
//using (conn)
{
conn.Open();
MySqlTransaction tran = conn.BeginTransaction();
@@ -211,6 +239,11 @@ namespace WindowsFormsApp1
tran.Rollback();
MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
if (conn != null && conn.State != System.Data.ConnectionState.Closed)
conn.Close();
}
}
}
/// <summary>

View File

@@ -71,6 +71,7 @@
this. = new System.Windows.Forms.ToolStripMenuItem();
this. = new System.Windows.Forms.ToolStripMenuItem();
this.1 = new System.Windows.Forms.ToolStripMenuItem();
this.2 = new System.Windows.Forms.ToolStripMenuItem();
this.iSBN조회 = new System.Windows.Forms.ToolStripMenuItem();
this.dVDCDLPToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this. = new System.Windows.Forms.ToolStripMenuItem();
@@ -131,7 +132,9 @@
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
this.IPText = new System.Windows.Forms.ToolStripLabel();
this.lblStatus = new System.Windows.Forms.ToolStripLabel();
this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog();
this.btDevDb = new System.Windows.Forms.ToolStripMenuItem();
this.menuStrip1.SuspendLayout();
this.panel1.SuspendLayout();
this.toolStrip1.SuspendLayout();
@@ -449,6 +452,7 @@
this.,
this.,
this.1,
this.2,
this.iSBN조회});
this..Name = "마크작업";
this..Size = new System.Drawing.Size(156, 22);
@@ -457,42 +461,49 @@
// 마크작성
//
this..Name = "마크작성";
this..Size = new System.Drawing.Size(146, 22);
this..Size = new System.Drawing.Size(154, 22);
this..Text = "마크 작성";
this..Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 마크목록
//
this..Name = "마크목록";
this..Size = new System.Drawing.Size(146, 22);
this..Size = new System.Drawing.Size(154, 22);
this..Text = "마크 목록";
this..Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 소장자료검색
//
this..Name = "소장자료검색";
this..Size = new System.Drawing.Size(146, 22);
this..Size = new System.Drawing.Size(154, 22);
this..Text = "소장자료검색";
this..Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 마크정리
//
this..Name = "마크정리";
this..Size = new System.Drawing.Size(146, 22);
this..Size = new System.Drawing.Size(154, 22);
this..Text = "마크 정리";
this..Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 복본조사1
//
this.1.Name = "복본조사1";
this.1.Size = new System.Drawing.Size(146, 22);
this.1.Size = new System.Drawing.Size(154, 22);
this.1.Text = "복본조사";
this.1.Click += new System.EventHandler(this.ToolStripMenuItem1_Click);
//
// 복본조사2
//
this.2.Name = "복본조사2";
this.2.Size = new System.Drawing.Size(154, 22);
this.2.Text = "복본조사(New)";
this.2.Click += new System.EventHandler(this.2_Click);
//
// iSBN조회
//
this.iSBN조회.Name = "iSBN조회";
this.iSBN조회.Size = new System.Drawing.Size(146, 22);
this.iSBN조회.Size = new System.Drawing.Size(154, 22);
this.iSBN조회.Text = "ISBN 조회";
this.iSBN조회.Click += new System.EventHandler(this.iSBN조회ToolStripMenuItem_Click);
//
@@ -751,7 +762,8 @@
this.ToolStripMenuItem,
this.ToolStripMenuItem,
this.ToolStripMenuItem,
this.ToolStripMenuItem});
this.ToolStripMenuItem,
this.btDevDb});
this.ToolStripMenuItem.Name = "마스터ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
this.ToolStripMenuItem.Text = "마스터";
@@ -761,48 +773,48 @@
this.ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.ToolStripMenuItem});
this.ToolStripMenuItem.Name = "이용자관리ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Size = new System.Drawing.Size(202, 22);
this.ToolStripMenuItem.Text = "이용자 관리";
//
// 신규사업자등록ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "신규사업자등록ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Size = new System.Drawing.Size(138, 22);
this.ToolStripMenuItem.Text = "사업자 관리";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 공지발송ToolStripMenuItem1
//
this.ToolStripMenuItem1.Name = "공지발송ToolStripMenuItem1";
this.ToolStripMenuItem1.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem1.Size = new System.Drawing.Size(202, 22);
this.ToolStripMenuItem1.Text = "공지 발송";
this.ToolStripMenuItem1.Click += new System.EventHandler(this.ToolStripMenuItem1_Click);
//
// 매출내역ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "매출내역ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Size = new System.Drawing.Size(202, 22);
this.ToolStripMenuItem.Text = "매출내역";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 이용자거래처조회ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "이용자거래처조회ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Size = new System.Drawing.Size(202, 22);
this.ToolStripMenuItem.Text = "이용자 거래처 조회";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 마크설정ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "마크설정ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Size = new System.Drawing.Size(202, 22);
this.ToolStripMenuItem.Text = "마크설정";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 일괄처리관리ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "일괄처리관리ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Size = new System.Drawing.Size(202, 22);
this.ToolStripMenuItem.Text = "일괄처리 관리";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
@@ -1008,7 +1020,8 @@
this.botUserLabel,
this.toolStripSeparator2,
this.toolStripSeparator3,
this.IPText});
this.IPText,
this.lblStatus});
this.toolStrip1.Location = new System.Drawing.Point(0, 681);
this.toolStrip1.Name = "toolStrip1";
this.toolStrip1.Size = new System.Drawing.Size(1259, 25);
@@ -1057,6 +1070,21 @@
this.IPText.Size = new System.Drawing.Size(154, 22);
this.IPText.Text = "접속 아이피 : 0.000.00.000";
//
// lblStatus
//
this.lblStatus.Name = "lblStatus";
this.lblStatus.Size = new System.Drawing.Size(27, 22);
this.lblStatus.Text = "WD";
//
// btDevDb
//
this.btDevDb.ForeColor = System.Drawing.Color.Blue;
this.btDevDb.Name = "btDevDb";
this.btDevDb.Size = new System.Drawing.Size(202, 22);
this.btDevDb.Text = "데이터베이스편집(개발)";
this.btDevDb.Visible = false;
this.btDevDb.Click += new System.EventHandler(this.btDevDb_Click);
//
// Main
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
@@ -1188,5 +1216,8 @@
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator3;
public System.Windows.Forms.ToolStripLabel IPText;
private System.Windows.Forms.ToolStripLabel lblStatus;
private System.Windows.Forms.ToolStripMenuItem 2;
private System.Windows.Forms.ToolStripMenuItem btDevDb;
}
}

View File

@@ -26,6 +26,8 @@ using UniMarc.회계;
using UniMarc.;
using UniMarc.Properties;
using UniMarc;
using UniMarc.;
namespace WindowsFormsApp1
{
@@ -40,6 +42,8 @@ namespace WindowsFormsApp1
public Main()
{
InitializeComponent();
PUB.Init();
this.btDevDb.Visible = System.Diagnostics.Debugger.IsAttached;
}
public string User_Name { get; internal set; }
@@ -48,13 +52,7 @@ namespace WindowsFormsApp1
{
this.Visible = false; // 메인폼을 먼저 숨김
#region "Log setting"
var logsubdir = "{yyyy|MM|dd}";
string tPath = string.Format("{0}\\LOG", AppDomain.CurrentDomain.BaseDirectory).Replace("\\\\", "\\"); ;
CUtill.mLog = new arUtil.Log(tPath);
CUtill.mLog.SubDirectory = logsubdir;
CUtill.mLog.FileNameFormat = "{yyMMdd}";
#endregion
login login = new login();
VersionText.Text = string.Format("UniMarc Ver.{0}", ip.VersionInfo());
@@ -99,9 +97,46 @@ namespace WindowsFormsApp1
SetBtnName();
}
catch (Exception ex) { MessageBox.Show(ex.ToString()); }
UpdaterCheck();
this.Text += $" (Build {Application.ProductVersion})";
}
/// <summary>
/// factory client update checkd
/// </summary>
static void UpdaterCheck()
{
var fi = new System.IO.FileInfo("_Factory_Client.exe");
var fio = new System.IO.FileInfo("Factory_Client.exe");
if (fi.Exists)
{
if (fio.Exists == false)
{
fi.CopyTo(fio.FullName);
fio.LastWriteTime = fi.LastWriteTime;
fio.LastAccessTime = fi.LastAccessTime;
}
else
{
//check size
if (fi.Length != fio.Length || fi.LastWriteTime != fio.LastWriteTime)
{
fi.CopyTo(fio.FullName, true);
fio.LastWriteTime = fi.LastWriteTime;
fio.LastAccessTime = fi.LastAccessTime;
}
}
Console.WriteLine("updater patch");
}
}
#region
public void SetBtnName()
{
@@ -141,7 +176,7 @@ namespace WindowsFormsApp1
};
Image[] source_Marc = {
Resources._3_1_1_마크작성, Resources._3_1_2_마크목록, Resources._3_1_3_소장자료검색, Resources._3_1_4_마크정리, Resources._3_1_5_복본조사, Resources._3_1_6_ISBN조회,
Resources._3_1_1_마크작성, Resources._3_1_2_마크목록, Resources._3_1_3_소장자료검색, Resources._3_1_4_마크정리, Resources._3_1_5_복본조사, Resources._3_1_5_복본조사, Resources._3_1_6_ISBN조회,
Resources._3_2_1_목록, Resources._3_2_2_편목,
Resources._3_2_1_반입, Resources._3_2_2_반출,
Resources._3_3_1_전집관리, Resources._3_3_2_저자기호,
@@ -154,7 +189,7 @@ namespace WindowsFormsApp1
};
string[] Marc = {
"마크 추가", "마크 목록", "소장자료검색", "마크 정리", "복본 조사", "ISBN 조회",
"마크 추가", "마크 목록", "소장자료검색", "마크 정리", "복본 조사","복본 조사(New)", "ISBN 조회",
"DVD/CD/LP 목록", "DVD/CD/LP 편목",
"반입", "반출",
"전집관리", "저자기호",
@@ -211,12 +246,12 @@ namespace WindowsFormsApp1
string[] Marc = {
"마크 추가", "마크 목록", "소장자료검색", "마크 정리", "ISBN 조회",
"DVD/CD/LP 목록", "DVD/CD/LP 편목",
"반입", "반출", "복본 조사", "DLS 복본 조사", "마크 수집", "전집관리",
"반입", "반출", "복본 조사", "복본 조사(New)","DLS 복본 조사", "마크 수집", "전집관리",
"검수", "저자기호", "DLS 조회 입력", "서류작성",
"마크통계", "장비관리" };
ToolStripMenuItem[] MarcT = {
, , , , iSBN조회, , , ,
, 1, dLS복본조사, , ,
, 1, 2, dLS복본조사, , ,
, , DLS조회, ,
,
};
@@ -246,7 +281,7 @@ namespace WindowsFormsApp1
bool IsText = false;
for(int b = 0; b < MenuTotal[a].Length; b++)
{
if (MenuTotal[a][b] == btnText) {
if (MenuTotal[a][b].Trim().ToLower() == btnText.Trim().ToLower()) {
IsText = true;
count[1] = b;
break;
@@ -1161,7 +1196,7 @@ namespace WindowsFormsApp1
{
Mac_dLS_Copy = new DLS_Copy(this);
Mac_dLS_Copy.MdiParent = this;
Mac_dLS_Copy.WindowState = FormWindowState.Maximized;
//Mac_dLS_Copy.WindowState = FormWindowState.Maximized;
Mac_dLS_Copy.FormClosed += (o, ea) => Mac_dLS_Copy = null;
Mac_dLS_Copy.Show();
}
@@ -1502,5 +1537,20 @@ namespace WindowsFormsApp1
}
}
#endregion
private void 2_Click(object sender, EventArgs e)
{
var Mac_check_Copy = new Check_copyWD(this);
Mac_check_Copy.MdiParent = this;
Mac_check_Copy.WindowState = FormWindowState.Normal;
Mac_check_Copy.FormClosed += (o, ea) => Mac_check_Copy = null;
Mac_check_Copy.Show();
}
private void btDevDb_Click(object sender, EventArgs e)
{
var f = new fDevDB();
f.Show();
}
}
}

31
unimarc/unimarc/PUB.cs Normal file
View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using UniMarc.Properties;
namespace UniMarc
{
public static class PUB
{
public static arUtil.Log log;
public static UserSetting setting;
public static void Init()
{
#region "Log setting"
var logsubdir = "{yyyy|MM|dd}";
string tPath = string.Format("{0}\\LOG", AppDomain.CurrentDomain.BaseDirectory).Replace("\\\\", "\\"); ;
PUB.log = new arUtil.Log(tPath);
PUB.log.SubDirectory = logsubdir;
PUB.log.FileNameFormat = "{yyMMdd}";
#endregion
setting = new UserSetting();
setting.Load();
}
}
}

View File

@@ -16,39 +16,25 @@ namespace WindowsFormsApp1
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//AR.UTIL.MsgE("unitmarc");
try
{
DB_InitSetting();
UpdaterCheck();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
Application.Run(new Main());
}
/// <summary>
/// factory client update checkd
/// </summary>
static void UpdaterCheck()
{
var fi = new System.IO.FileInfo("_Factory_Client.exe");
var fio = new System.IO.FileInfo("Factory_Client.exe");
if (fi.Exists)
{
if (fio.Exists == false)
{
fi.CopyTo(fio.FullName);
fio.LastWriteTime = fi.LastWriteTime;
fio.LastAccessTime = fi.LastAccessTime;
}
else
{
//check size
if(fi.Length != fio.Length || fi.LastWriteTime != fio.LastWriteTime)
{
fi.CopyTo(fio.FullName,true);
fio.LastWriteTime = fi.LastWriteTime;
fio.LastAccessTime = fi.LastAccessTime;
}
}
Console.WriteLine("updater patch");
}
}
static void DB_InitSetting()
{
UniMarc.Properties.Settings.Default.IP = ConvertIP(UniMarc.Properties.Settings.Default.IP);

View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를
// 기본값으로 할 수 있습니다.
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.185")]
[assembly: AssemblyFileVersion("1.0.0.185")]
[assembly: AssemblyVersion("2025.08.13.1200")]
[assembly: AssemblyFileVersion("2025.08.13.1200")]

View File

@@ -0,0 +1,644 @@
using AngleSharp.Dom;
using AR;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Chromium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
using System;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Web.UI.WebControls;
using System.Xml.Linq;
using UniMarc.SearchModel;
using UniMarc.;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
namespace BokBonCheck
{
public class AnsanLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://lib.ansan.go.kr/DetailSearch";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public AnsanLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"안산시립({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
// 브라우저 배율을 80%로 설정 (브라우저가 표시되는 경우에만)
if (showdriver)
{
try
{
((IJavaScriptExecutor)_driver).ExecuteScript("document.body.style.zoom = '0.8';");
Console.WriteLine("브라우저 배율을 80%로 설정했습니다.");
}
catch (Exception zoomEx)
{
Console.WriteLine($"브라우저 배율 설정 실패: {zoomEx.Message}");
}
}
Console.WriteLine("AnsanLibSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"AnsanLibSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"AnsanLibSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
try
{
Console.WriteLine("도서관 선택 과정 시작...");
// 1. 열기버튼 찾기 (작은도서관 체크박스가 보이도록)
var element = wait.Until(d => d.FindElement(By.CssSelector(".btIco.plus")));
if (element != null)
{
SafeClick(element);
}
// 2. 현재 체크된 상태 확인
var allCheckboxes = wait.Until(d => d.FindElements(By.CssSelector("div.library input[type='checkbox']")));
var checkedCheckboxes = allCheckboxes.Where(cb => cb.Selected).ToList();
Console.WriteLine($"현재 체크된 체크박스 개수: {checkedCheckboxes.Count}");
// 3. 지정한 도서관만 정확히 1개 체크되어 있는지 확인
bool isTargetOnlyChecked = false;
if (!string.IsNullOrEmpty(AreaCode))
{
if (checkedCheckboxes.Count == 1)
{
var onlyChecked = checkedCheckboxes[0];
var onlyCheckedValue = onlyChecked.GetAttribute("value");
if (onlyCheckedValue == AreaCode)
{
Console.WriteLine($"✓ {AreaCode} 도서관만 정확히 선택되어 있음 - 완료");
isTargetOnlyChecked = true;
}
}
}
// 4. 목표 상태가 아니면 전체선택 버튼으로 모두 해제
if (!isTargetOnlyChecked)
{
Console.WriteLine("전체선택 버튼을 이용해서 모든 체크박스 해제 중...");
// 전체선택 버튼들 찾기 (총 2개)
var allSelectButtons = wait.Until(d => d.FindElements(By.CssSelector("span.btnAllSelect")));
Console.WriteLine($"전체선택 버튼 {allSelectButtons.Count}개 발견");
// 모든 전체선택 버튼 클릭해서 해제
foreach (var button in allSelectButtons)
{
try
{
Console.WriteLine("전체선택 버튼 클릭 중...");
SafeClick(button);
Thread.Sleep(50);
}
catch (Exception ex)
{
Console.WriteLine($"전체선택 버튼 클릭 오류: {ex.Message}");
}
}
// 해제 후 체크된 체크박스 개수 확인
var remainingChecked = allCheckboxes.Where(cb => cb.Selected).Count();
Console.WriteLine($"전체선택 버튼 클릭 후 체크된 체크박스 개수: {remainingChecked}");
// 5. 지정 도서관만 체크
if (!string.IsNullOrEmpty(AreaCode))
{
Console.WriteLine($"목표 도서관 '{AreaCode}' 선택 중...");
try
{
var targetCheckbox = wait.Until(d => d.FindElement(By.CssSelector($"div.library input[type='checkbox'][value='{AreaCode}']")));
if (!targetCheckbox.Selected)
{
Console.WriteLine($"{AreaCode} 도서관 체크 중...");
SafeClick(targetCheckbox);
Thread.Sleep(300);
}
// 최종 선택 상태 확인
if (targetCheckbox.Selected)
{
Console.WriteLine($"✓ {AreaCode} 도서관 선택 완료");
}
else
{
Console.WriteLine($"✗ {AreaCode} 도서관 선택 실패");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"목표 도서관 선택 실패: {ex.Message}");
return false;
}
}
else
{
Console.WriteLine("AreaCode가 비어있음 - 전체 도서관으로 검색");
}
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 실패: {ex.Message}");
return false;
}
}
protected void SafeClick(IWebElement element)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. 요소가 보이도록 스크롤 후 일반 클릭
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].scrollIntoView(true);", element);
Thread.Sleep(300);
element.Click();
}
catch
{
try
{
// 2. JavaScript로 클릭 시도
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].click();", element);
}
catch
{
try
{
// 3. Actions 클래스 사용
var actions = new Actions(_driver);
actions.MoveToElement(element).Click().Perform();
}
catch
{
try
{
// 4. 체크박스의 경우 직접 체크 상태 변경
if (element.TagName.ToLower() == "input" && element.GetAttribute("type") == "checkbox")
{
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].checked = !arguments[0].checked;", element);
}
else
{
// 일반 요소는 강제 클릭
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].dispatchEvent(new Event('click'));", element);
}
}
catch (Exception ex)
{
Console.WriteLine($"모든 클릭 방법 실패: {ex.Message}");
}
}
}
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
var cururl = _driver.Url;
if (cururl.Equals(SiteUrl) == false)
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 페이지 로드 후 브라우저 배율을 80%로 설정 (브라우저가 표시되는 경우에만)
try
{
if (_driver.Manage().Window.Size.Width > 0) // 브라우저가 표시되는 경우
{
((IJavaScriptExecutor)_driver).ExecuteScript("document.body.style.zoom = '0.8';");
Console.WriteLine("페이지 로드 후 브라우저 배율을 80%로 설정했습니다.");
}
}
catch (Exception zoomEx)
{
Console.WriteLine($"페이지 배율 설정 실패: {zoomEx.Message}");
}
// 도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색어 입력
try
{
// 검색 입력창이 상호작용 가능할 때까지 대기
var searchInput = wait.Until(d => d.FindElement(By.Id("advTitle")));
var dispaly = searchInput.Displayed;
var enabled = searchInput.Enabled;
// 요소가 보이도록 스크롤
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].scrollIntoView(true);", searchInput);
Thread.Sleep(300);
// readonly 속성이 있다면 제거
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].removeAttribute('readonly');", searchInput);
// 클릭해서 포커스 맞추기
try
{
searchInput.Click();
Thread.Sleep(200);
}
catch { }
// 기존 값 제거 (여러 방법 시도)
try
{
searchInput.Clear();
}
catch
{
// Clear가 실패하면 JavaScript로 값 제거
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].value = '';", searchInput);
}
// 검색어 입력 (여러 방법 시도)
try
{
searchInput.SendKeys(searchTerm);
}
catch
{
// SendKeys가 실패하면 JavaScript로 값 설정
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].value = arguments[1];", searchInput, searchTerm);
// input 이벤트 발생시켜서 웹페이지에 변경사항 알림
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].dispatchEvent(new Event('input'));", searchInput);
}
Console.WriteLine($"검색어 '{searchTerm}' 입력 완료");
}
catch (Exception ex)
{
result.ErrorMessage = $"검색어입력실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색 버튼 클릭
try
{
Console.WriteLine("검색 버튼 찾기 시작...");
((IJavaScriptExecutor)_driver).ExecuteScript(@"
// 검색 버튼을 여러 방법으로 찾아서 클릭
var searchButtons = document.querySelectorAll('a.btTxt.bg.primary');
for (var i = 0; i < searchButtons.length; i++) {
var btn = searchButtons[i];
if (btn.innerText.trim() === '검색' || btn.innerText.includes('검색')) {
btn.click();
console.log('검색 버튼 클릭 성공');
break;
}
}
// 위 방법이 실패하면 마지막 버튼 클릭
if (searchButtons.length >= 2) {
searchButtons[searchButtons.length - 1].click();
console.log('마지막 버튼 클릭 성공');
}
");
}
catch (Exception ex)
{
result.ErrorMessage = $"검색버튼클릭실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
SearchComplete:
Console.WriteLine("검색 실행 완료, 페이지 로딩 대기 중...");
if (this._driver.Url.EndsWith("DetailSearchResult") == false)
{
while (true)
{
await Task.Delay(100);
}
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
if (driver.Url.EndsWith("DetailSearchResult") == false)
{
errmessage = "결과페이지가아님";
return -1;
}
try
{
// 1. 검색결과가 없는 경우 확인
try
{
var noResultElements = driver.FindElements(By.XPath("//*[contains(text(), '검색결과가 없습니다') or contains(text(), '검색된 자료가 없습니다')]"));
if (noResultElements.Count > 0)
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
RetryPoint:
bool retry = false;
// 2. 안산시 도서관 특화: span.count.gothic em 요소에서 직접 추출
try
{
var countElement = driver.FindElement(By.CssSelector("span.count.gothic em"));
if (countElement != null)
{
// Text 대신 InnerHtml이나 GetAttribute 사용해보기
var countText = countElement.Text.Trim();
var innerHTML = countElement.GetAttribute("innerHTML");
var outerHTML = countElement.GetAttribute("outerHTML");
Console.WriteLine($"count 요소 .Text: '{countText}'");
Console.WriteLine($"count 요소 innerHTML: '{innerHTML}'");
Console.WriteLine($"count 요소 outerHTML: '{outerHTML}'");
// innerHTML이나 outerHTML에서 텍스트 추출 시도
string textToUse = !string.IsNullOrEmpty(countText) ? countText : innerHTML;
if (string.IsNullOrEmpty(textToUse))
{
textToUse = outerHTML;
}
Console.WriteLine($"추출할 텍스트: '{textToUse}'");
// "총 0 건 " 형태에서 숫자 추출
var match = Regex.Match(textToUse, @"총\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success && int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
Console.WriteLine("검색 결과: 0건");
return 0;
}
errmessage = $"검색성공({count}권)";
Console.WriteLine($"검색 결과: {count}건");
return count;
}
else
{
Console.WriteLine($"정규식 매칭 실패: '{textToUse}'");
}
}
}
catch (Exception ex1)
{
Console.WriteLine($"span.count.gothic em 요소 추출 실패: {ex1.Message}");
}
// 3. span.count 전체에서 추출 시도
try
{
var countSpan = driver.FindElement(By.CssSelector("span.count"));
if (countSpan != null)
{
var fullText = countSpan.Text.Trim();
Console.WriteLine($"count span 전체 텍스트: '{fullText}'");
var match = Regex.Match(fullText, @"총\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success && int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
catch (Exception ex2)
{
Console.WriteLine($"span.count 요소 추출 실패: {ex2.Message}");
}
// 3. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("총 0 건") ||
pageSource.Contains("총 0건"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기 (안산시 도서관 특화)
var htmlPatterns = new[]
{
@"총\s*(\d+)\s*건",
@"<em[^>]*>총\s*(\d+)\s*건\s*</em>",
@"검색결과\s*:\s*총\s*(\d+)\s*건",
@"전체\s*(\d+)\s*건",
@"검색결과\s*총\s*(\d+)\s*건",
@"검색\s*결과\s*\(\s*총\s*(\d+)\s*건\s*\)",
@"총\s*<[^>]*>(\d+)</[^>]*>\s*건"
};
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
if (retry == false)
{
Console.WriteLine( "결과를 찾을 수 없어 재시도");
retry = true;
Task.Delay(1000);
goto RetryPoint;
}
else
{
errmessage = "결과수량을찾을수없음";
return -1;
}
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 검색 결과 페이지가 로드될 때까지 대기
wait.Until(d =>
{
try
{
var pageSource = d.PageSource;
// 검색 결과나 관련 요소가 나타나면 로드 완료
return pageSource.Contains("검색결과") ||
pageSource.Contains("총") ||
pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("건");
}
catch
{
return false;
}
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(3000);
Console.WriteLine($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
namespace BokBonCheck
{
public class BookSearchResult
{
public string SiteName { get; set; }
public int BookCount { get; set; }
public string SearchTerm { get; set; }
public DateTime SearchTime { get; set; }
public string ErrorMessage { get; set; }
public bool IsSuccess { get; set; }
}
public class BookSearchService
{
private readonly List<ILibrarySearcher> _searchers;
public BookSearchService()
{
_searchers = new List<ILibrarySearcher>
{
};
}
/// <summary>
/// Return Searcher
/// </summary>
/// <param name="no"></param>
/// <returns></returns>
public ILibrarySearcher Get(int no)
{
return _searchers.Where(t => t.No == no).FirstOrDefault();
}
public ILibrarySearcher Get(string no)
{
return _searchers.Where(t => t.SiteName == no).FirstOrDefault();
}
public void AddSearcher(ILibrarySearcher searcher)
{
if (!_searchers.Any(s => s.SiteName == searcher.SiteName))
{
_searchers.Add(searcher);
}
}
public void RemoveSearcher(string siteName)
{
var searcher = _searchers.FirstOrDefault(s => s.SiteName == siteName);
if (searcher != null)
{
_searchers.Remove(searcher);
}
}
public List<string> GetAvailableSites()
{
return _searchers.Select(s => s.SiteName).ToList();
}
public void ClearSearchers()
{
_searchers.Clear();
}
}
}

View File

@@ -0,0 +1,422 @@
using AR;
using AR.Dialog;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Chromium;
using OpenQA.Selenium.Support.UI;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using UniMarc.SearchModel;
using UniMarc.;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
namespace BokBonCheck
{
public class BukguLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl { get; protected set; } = "https://lib.bukgu.gwangju.kr/main/bookSearchSmartlib.do?PID=0301";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public BukguLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"광주북구스마트도서관({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
Console.WriteLine("BukguLibSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"BukguLibSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"BukguLibSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
try
{
// 특정 도서관 선택인 경우
var targetCheckboxId = $"lib{AreaCode}";
var targetCheckbox = wait.Until(d => d.FindElement(By.Id(targetCheckboxId)));
if (targetCheckbox == null)
{
Console.WriteLine($"도서관 체크박스를 찾을 수 없습니다: {targetCheckboxId}");
return false;
}
// 다른 체크박스들 해제
var allCheckboxes = wait.Until(d => d.FindElements(By.CssSelector("input[name='libCode']")));
foreach (var checkbox in allCheckboxes)
{
if (checkbox.GetAttribute("id") != targetCheckboxId && checkbox.Selected)
{
SafeClick(checkbox);
Thread.Sleep(100);
}
}
// 대상 체크박스 선택
if (!targetCheckbox.Selected)
{
SafeClick(targetCheckbox);
Thread.Sleep(300);
Console.WriteLine($"{AreaCode} 도서관으로 변경됨");
}
else
{
Console.WriteLine($"{AreaCode} 도서관이 이미 선택되어 있음");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 실패: {ex.Message}");
return false;
}
}
protected void SafeClick(IWebElement element)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. JavaScript로 클릭 시도
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click();", element);
}
catch
{
try
{
// 2. 요소가 보이도록 스크롤 후 클릭
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", element);
Thread.Sleep(500);
element.Click();
}
catch
{
try
{
// 3. Actions 클래스 사용
var driver = ((IWrapsDriver)element).WrappedDriver;
var actions = new OpenQA.Selenium.Interactions.Actions(driver);
actions.MoveToElement(element).Click().Perform();
}
catch
{
// 4. 마지막 방법: JavaScript로 직접 체크 상태 변경
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].checked = !arguments[0].checked;", element);
}
}
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
var cururl = _driver.Url;
if (cururl.Equals(SiteUrl) == false)
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색어 입력 - 다양한 선택자로 시도
try
{
IWebElement searchInput = null;
try
{
searchInput = wait.Until(d => d.FindElement(By.CssSelector("input[name='searchText']")));
}
catch { }
if (searchInput == null)
{
result.ErrorMessage = "검색창을 찾을 수 없음";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
searchInput.Clear();
searchInput.SendKeys(searchTerm);
}
catch (Exception ex)
{
result.ErrorMessage = $"검색어입력실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색 버튼 클릭 - 다양한 선택자로 시도
try
{
IWebElement searchButton = null;
try
{
searchButton = wait.Until(d => d.FindElement(By.CssSelector("button[type='submit']")));
}
catch { }
if (searchButton == null)
{
result.ErrorMessage = "검색버튼을 찾을 수 없음";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
searchButton.Click();
}
catch (Exception ex)
{
result.ErrorMessage = $"검색버튼클릭실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
// 1. totalCount 요소에서 직접 추출 시도
try
{
var totalCountElement = driver.FindElement(By.CssSelector("p.totalCount"));
if (totalCountElement != null)
{
// strong 태그에서 직접 숫자 추출 시도
try
{
var strongElement = totalCountElement.FindElement(By.TagName("strong"));
if (strongElement != null)
{
var countText = strongElement.Text.Trim();
if (int.TryParse(countText, out int strongCount))
{
if (strongCount == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({strongCount}권)";
return strongCount;
}
}
}
catch { }
// 전체 텍스트에서 정규식으로 추출
var totalCountText = totalCountElement.Text;
var match = Regex.Match(totalCountText, @"전체\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"totalCount 요소 검색 중 오류: {ex.Message}");
}
// 2. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("전체 0 건"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기
var htmlPatterns = new[]
{
@"전체\s*<strong>\s*(\d+)\s*</strong>\s*건",
@"전체\s+(\d+)\s+건",
@"총\s*(\d+)\s*권",
@"총\s*(\d+)\s*건",
@"검색결과\s*:\s*(\d+)"
};
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
errmessage = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 검색 결과 페이지가 로드될 때까지 대기
wait.Until(d =>
{
try
{
var pageSource = d.PageSource;
// 검색 결과나 관련 요소가 나타나면 로드 완료
return pageSource.Contains("searchTitle") ||
pageSource.Contains("totalCount") ||
pageSource.Contains("검색결과") ||
pageSource.Contains("전체") ||
pageSource.Contains("자료가");
}
catch
{
return false;
}
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(3000);
Console.WriteLine($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,30 @@
using AR;
using AR.Dialog;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Chromium;
using OpenQA.Selenium.Support.UI;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using UniMarc.SearchModel;
using UniMarc.;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
namespace BokBonCheck
{
public class BukguPublicLibSearcher : BukguLibSearcher
{
public BukguPublicLibSearcher(int no, string areaCode, string areaName) : base(no, areaCode, areaName)
{
this.SiteName = $"광주북구공공도서관({areaName})";
base.SiteUrl = "https://lib.bukgu.gwangju.kr/main/bookSearch.do?PID=0301";
}
}
}

View File

@@ -0,0 +1,30 @@
using AR;
using AR.Dialog;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Chromium;
using OpenQA.Selenium.Support.UI;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using UniMarc.SearchModel;
using UniMarc.;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
namespace BokBonCheck
{
public class BukguSlibSearcher : BukguLibSearcher
{
public BukguSlibSearcher(int no, string areaCode, string areaName) : base(no, areaCode, areaName)
{
this.SiteName = $"광주북구작은도서관({areaName})";
base.SiteUrl = "https://lib.bukgu.gwangju.kr/main/bookSearchSlib.do?PID=0301";
}
}
}

View File

@@ -0,0 +1,525 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using UniMarc.;
using OpenQA.Selenium.Chromium;
using UniMarc.SearchModel;
using System.Runtime.CompilerServices;
namespace BokBonCheck
{
public class DLSSearcher : ILibrarySearcher
{
public bool HttpApiMode { get; set; } = false;
public string SiteName { get; protected set; } = "DLS";
public string SiteUrl => "https://dls1.edunet.net/DLS/bookMng/bookMain";
public int No { get; set; }
private ChromiumDriver _driver;
public DLSSearcher(int no)
{
this.No = no;
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showBrowser = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showBrowser);
Console.WriteLine("DLSSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"DLSSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"DLSSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
IWebElement searchBox = null;
try
{
var selector = "#clickAll";
searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector)));
if (searchBox == null) return false;
if (searchBox.Selected == false)
{
SafeClick(searchBox);
}
return true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return false;
}
}
protected void SafeClick(IWebElement searchBox)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. JavaScript로 클릭 시도
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click();", searchBox);
}
catch
{
try
{
// 2. 요소가 보이도록 스크롤 후 클릭
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", searchBox);
Thread.Sleep(500);
searchBox.Click();
}
catch
{
try
{
// 3. Actions 클래스 사용
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
var actions = new OpenQA.Selenium.Interactions.Actions(driver);
actions.MoveToElement(searchBox).Click().Perform();
}
catch
{
// 4. 마지막 방법: JavaScript로 직접 체크 해제
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].checked = false;", searchBox);
}
}
}
}
public async Task<(Boolean, string)> Login(string id, string pw, bool showBrowser = false)
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver(showBrowser);
}
if (_driver.Url.StartsWith("data:"))
{
_driver.Navigate().GoToUrl(SiteUrl);
}
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
if (_driver.Url.EndsWith("loginMain"))
{
IWebElement idBox = wait.Until(d => d.FindElement(By.CssSelector("#lgID")));
if (idBox == null)
{
return (false, "로그인화면(ID찾기실패)");
}
idBox.Clear();
idBox.SendKeys(id);
IWebElement pwBox = wait.Until(t => t.FindElement(By.CssSelector("#lgPW")));
if (idBox == null)
{
return (false, "로그인화면(PW찾기실패)");
}
pwBox.Clear();
pwBox.SendKeys(pw);
//로그인버튼찾기
IWebElement btLogin = wait.Until(t => t.FindElement(By.CssSelector("#loginBtn")));
if (idBox == null)
{
return (false, "로그인화면(로그인버튼찾기실패)");
}
SafeClick(btLogin);
}
wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
var retrycnt = 0;
while (true)
{
if (_driver.Url.EndsWith("/bookMain") == false)
{
retrycnt += 1;
Console.WriteLine($"도서검색화면으로 이동({retrycnt})");
_driver.Navigate().GoToUrl(this.SiteUrl);
wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
await Task.Delay(1000);
}
else break;
}
Console.WriteLine("DLS 로그인 완료");
return (true, "");
}
string ID, PW, SCHOOL, STYPE;
public void SetParameter(string id, string pw, string schoolname, string searchtype)
{
this.ID = id;
this.PW = pw;
this.SCHOOL = schoolname;
this.STYPE = searchtype;
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
if (string.IsNullOrEmpty(searchTerm.Trim()))
{
result.ErrorMessage = "검색어없음";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
//현재url이 로그인 uRL이라면 로그인을 먼저한다
//https://dls1.edunet.net/DLS/loginMain
//ID : #lgID , PW: #lgPW, Button : #loginBtn
if (_driver.Url.EndsWith("loginMain"))
{
result.ErrorMessage = "로그인안됨";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
var retcnt = 0;
while (_driver.Url.EndsWith("/bookMain") == false)
{
retcnt += 1;
if (retcnt > 3)
{
//3회이상 실패하면 오류 처리한다.
result.ErrorMessage = "검색페이지이동불가";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
else
{
//검색페이지로 이동시키고
_driver.Navigate().GoToUrl(this.SiteUrl);
new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
await Task.Delay(1000);
}
}
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
//초등학교명 일치 확인
try
{
var selectSchool = wait.Until(t => t.FindElement(By.CssSelector("#current_school_select")));
if (selectSchool == null)
{
result.ErrorMessage = "학교선택칸없음";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
else
{
Console.WriteLine($"학교명:{this.SCHOOL}");
var select = new SelectElement(selectSchool);
var curTxt = select.SelectedOption.Text;
var curVal = select.SelectedOption.GetAttribute("value");
if (curTxt.Contains(this.SCHOOL) == false && this.SCHOOL.Contains(curTxt) == false)
{
select.SelectByText(this.SCHOOL);
}
else
{
//이름이 포함되어있다.
Console.WriteLine($"지정학교:{this.SCHOOL}, 현재선택학교:{curTxt}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"학교명일치확인 실패 {ex.Message}");
}
//검색방법(ISBN,서명) 확인 #search_field_first, title, ea_isbn
var selectType = wait.Until(t => t.FindElement(By.CssSelector("#search_field_first")));
if (selectType == null)
{
result.ErrorMessage = "검색방법없음";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
else
{
var select = new SelectElement(selectType);
var curTxt = select.SelectedOption.Text;
var curVal = select.SelectedOption.GetAttribute("value");
Console.WriteLine($"지정방법:{this.STYPE}, 현재선택방법:{curVal}");
if (this.STYPE.Equals(curVal) == false)
select.SelectByValue(this.STYPE);
}
//검색어입력
var tbSearch = wait.Until(t => t.FindElement(By.CssSelector("#search_first")));
if (tbSearch == null)
{
result.ErrorMessage = "검색어입력칸없음";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
else
{
tbSearch.Clear();
tbSearch.SendKeys(searchTerm);
}
wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
await Task.Delay(200);
//검색실행
var butSearch = wait.Until(t => t.FindElement(By.CssSelector("#book_search_btn")));
SafeClick(butSearch);
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
var msgElement = driver.FindElement(By.CssSelector(".modal-body[name='msg_txt']"));
if (msgElement != null)
{
string messageText = msgElement.Text;
bool hasNoDataMessage = messageText.Contains("검색된 자료가 없습니다");
if (hasNoDataMessage)
{
//다이얼로를 닫아준다.
//< button type = "button" class="btn" name="msg_btn_1">닫기</button>
try
{
var buttomelm = driver.FindElement(By.CssSelector("button[name='msg_btn_1']"));
if (buttomelm != null)
{
SafeClick(buttomelm);
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
Thread.Sleep(200);
}
}
catch
{
}
errmessage = "검색결과없음";
return 0;
}
}
}
catch (Exception ex)
{
}
try
{
// result 클래스 div에서 텍스트 추출
var resultElement = driver.FindElement(By.CssSelector("div.result"));
if (resultElement != null)
{
string resultText = resultElement.Text; // "1 - 30 of 684"
// 정규식으로 마지막 숫자 추출
var match = System.Text.RegularExpressions.Regex.Match(resultText, @"of\s+(\d+)");
if (match.Success)
{
int totalCount = int.Parse(match.Groups[1].Value);
Console.WriteLine($"전체 책 개수: {totalCount}");
errmessage = "";
return totalCount;
}
else
{
errmessage = "수량추출실패";
return -1;
}
}
}
catch (Exception ex)
{
}
errmessage = "결과확인실패";
return -1;
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 방법 4: 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 방법 5: 특정 텍스트가 페이지에 나타날 때까지 대기
wait.Until(d =>
{
try
{
var elm = d.FindElement(By.CssSelector(".modal-body[name='msg_txt']"));
if (elm == null)
{
//모달창이 없을때
//#pagersearch_book_grid > div > div:nth-child(1) > div
var byclassnameRlt = By.CssSelector("#pagersearch_book_grid > div > div:nth-child(1) > div > strong:nth-child(2)");
var elmRlt = d.FindElement(byclassnameRlt);
if (elmRlt == null)
{
return false;
}
else
{
Console.WriteLine($"찾은갯수:{elmRlt.Text}");
return true;
}
}
else
{
var pageText = elm.Text;
if (pageText.Contains("검색된 자료가 없습니다")) return true;
else return false;
}
//return pageText.Contains("에 대하여") && pageText.Contains("검색되었습니다");
}
catch (Exception ex)
{
//Console.WriteLine(ex.Message);
//return false;
}
try
{
//모달창이 없을때
//#pagersearch_book_grid > div > div:nth-child(1) > div
var byclassnameRlt = By.CssSelector("#pagersearch_book_grid > div > div:nth-child(1) > div > strong:nth-child(2)");
var elmRlt = d.FindElement(byclassnameRlt);
if (elmRlt == null)
{
return false;
}
else
{
Console.WriteLine($"찾은갯수:{elmRlt.Text}");
return true;
}
//return pageText.Contains("에 대하여") && pageText.Contains("검색되었습니다");
}
catch (Exception ex)
{
//Console.WriteLine(ex.Message);
//return false;
}
return false;
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(2000);
throw new Exception($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,142 @@
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading.Tasks;
namespace BokBonCheck
{
public partial class DownloadProgressForm : Form
{
private ProgressBar progressBar;
private Label lblStatus;
private Label lblProgress;
private Button btnCancel;
private bool isCancelled = false;
public DownloadProgressForm()
{
InitializeComponent();
this.StartPosition = FormStartPosition.CenterScreen;
}
private void InitializeComponent()
{
this.Text = "Chrome 드라이버 다운로드";
this.Size = new Size(400, 150);
this.StartPosition = FormStartPosition.CenterParent;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.ControlBox = false;
this.TopMost = true;
// 상태 라벨
lblStatus = new Label
{
Text = "Chrome 드라이버를 확인하고 있습니다...",
Location = new Point(20, 20),
Size = new Size(360, 20),
Font = new Font("맑은 고딕", 9),
TextAlign = ContentAlignment.MiddleCenter
};
// 진행률 라벨
lblProgress = new Label
{
Text = "0%",
Location = new Point(20, 50),
Size = new Size(360, 20),
Font = new Font("맑은 고딕", 10, FontStyle.Bold),
TextAlign = ContentAlignment.MiddleCenter,
ForeColor = Color.Blue
};
// 프로그레스 바
progressBar = new ProgressBar
{
Location = new Point(20, 80),
Size = new Size(360, 25),
Style = ProgressBarStyle.Continuous,
Minimum = 0,
Maximum = 100,
Value = 0
};
// 취소 버튼
btnCancel = new Button
{
Text = "취소",
Location = new Point(150, 115),
Size = new Size(100, 30),
Font = new Font("맑은 고딕", 9),
BackColor = Color.LightCoral
};
btnCancel.Click += BtnCancel_Click;
// 컨트롤 추가
this.Controls.AddRange(new Control[]
{
lblStatus, lblProgress, progressBar, btnCancel
});
}
private void BtnCancel_Click(object sender, EventArgs e)
{
isCancelled = true;
btnCancel.Enabled = false;
lblStatus.Text = "취소 중...";
}
public bool IsCancelled => isCancelled;
public void UpdateProgress(int percentage, string status = null)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<int, string>(UpdateProgress), percentage, status);
return;
}
progressBar.Value = Math.Min(percentage, 100);
lblProgress.Text = $"{percentage}%";
if (!string.IsNullOrEmpty(status))
{
lblStatus.Text = status;
}
}
public void SetCompleted(string message = "다운로드 완료!")
{
if (this.InvokeRequired)
{
this.Invoke(new Action<string>(SetCompleted), message);
return;
}
progressBar.Value = 100;
lblProgress.Text = "100%";
lblStatus.Text = message;
btnCancel.Text = "확인";
btnCancel.BackColor = Color.LightGreen;
btnCancel.Enabled = true;
}
public void SetError(string errorMessage)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<string>(SetError), errorMessage);
return;
}
lblStatus.Text = "오류 발생";
lblProgress.Text = "실패";
lblProgress.ForeColor = Color.Red;
btnCancel.Text = "확인";
btnCancel.BackColor = Color.LightCoral;
btnCancel.Enabled = true;
}
}
}

View File

@@ -0,0 +1,423 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using UniMarc.;
using OpenQA.Selenium.Chromium;
using UniMarc.SearchModel;
using System.Runtime.CompilerServices;
using AR;
namespace BokBonCheck
{
public class GoheungLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://www.ghlib.go.kr/BookSearch/detail";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public GoheungLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"고흥군립({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
Console.WriteLine("GoheungLibSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"GoheungLibSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"GoheungLibSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
try
{
// 전체 선택인 경우
if (string.IsNullOrEmpty(AreaCode) || AreaCode == "ALL")
{
var allRadio = wait.Until(d => d.FindElement(By.Id("libALL")));
if (!allRadio.Selected)
{
SafeClick(allRadio);
Thread.Sleep(300);
Console.WriteLine("전체도서관 선택됨");
}
else
{
Console.WriteLine("전체도서관이 이미 선택되어 있음");
}
return true;
}
// 특정 도서관 선택인 경우
var targetRadioId = $"lib{AreaCode}";
var targetRadio = wait.Until(d => d.FindElement(By.Id(targetRadioId)));
if (targetRadio == null)
{
Console.WriteLine($"도서관 라디오 버튼을 찾을 수 없습니다: {targetRadioId}");
return false;
}
// 이미 선택되어 있지 않으면 선택
if (!targetRadio.Selected)
{
SafeClick(targetRadio);
Thread.Sleep(300);
Console.WriteLine($"{AreaCode} 도서관으로 변경됨");
}
else
{
Console.WriteLine($"{AreaCode} 도서관이 이미 선택되어 있음");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 실패: {ex.Message}");
return false;
}
}
protected void SafeClick(IWebElement element)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. JavaScript로 클릭 시도
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click();", element);
}
catch
{
try
{
// 2. 요소가 보이도록 스크롤 후 클릭
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", element);
Thread.Sleep(500);
element.Click();
}
catch
{
try
{
// 3. Actions 클래스 사용
var driver = ((IWrapsDriver)element).WrappedDriver;
var actions = new OpenQA.Selenium.Interactions.Actions(driver);
actions.MoveToElement(element).Click().Perform();
}
catch
{
// 4. 마지막 방법: JavaScript로 직접 체크
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].checked = true;", element);
}
}
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
var cururl = _driver.Url;
if (cururl.Equals(SiteUrl) == false)
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 도서명 입력
try
{
var titleInput = wait.Until(d => d.FindElement(By.Id("queryTitle")));
titleInput.Clear();
titleInput.SendKeys(searchTerm);
}
catch (Exception ex)
{
result.ErrorMessage = $"도서명입력실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색 버튼 클릭
try
{
var searchButton = wait.Until(d => d.FindElement(By.CssSelector("button[type='submit']")));
searchButton.Click();
}
catch (Exception ex)
{
result.ErrorMessage = $"검색버튼클릭실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
// 1. 먼저 totalCount 요소에서 직접 추출 시도
try
{
var totalCountElement = driver.FindElement(By.CssSelector("p.totalCount"));
if (totalCountElement != null)
{
var totalCountText = totalCountElement.Text; // 예: "전체 1 건"
var match = Regex.Match(totalCountText, @"전체\s+(\d+)\s+건", RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
// strong 태그에서 직접 숫자 추출 시도
try
{
var strongElement = totalCountElement.FindElement(By.TagName("strong"));
if (strongElement != null && int.TryParse(strongElement.Text, out int strongCount))
{
if(strongCount == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({strongCount}권)";
return strongCount;
}
}
catch { }
}
}
catch (Exception ex)
{
Console.WriteLine($"totalCount 요소 검색 중 오류: {ex.Message}");
}
// 2. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("전체 0 건"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 "전체 <strong>N</strong> 건" 패턴 찾기
var htmlPatterns = new[]
{
@"전체\s*<strong>(\d+)</strong>\s*건",
@"전체\s+(\d+)\s+건",
@"총\s*(\d+)\s*권",
@"총\s*(\d+)\s*건"
};
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
// 3. 결과 테이블이나 리스트가 있는지 확인
try
{
var resultElements = driver.FindElements(By.CssSelector(".bookList, .searchResult, .result-list, table tbody tr"));
if (resultElements.Count > 0)
{
// 테이블 헤더나 빈 행을 제외한 실제 결과 개수 계산
var actualCount = 0;
foreach (var element in resultElements)
{
var text = element.Text?.Trim();
if (!string.IsNullOrEmpty(text) &&
!text.Contains("도서명") &&
!text.Contains("저자") &&
!text.Contains("출판사"))
{
actualCount++;
}
}
if (actualCount > 0)
{
errmessage = $"검색성공({actualCount}권)";
return actualCount;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"결과 요소 검색 중 오류: {ex.Message}");
}
errmessage = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 검색 결과 페이지가 로드될 때까지 대기
wait.Until(d =>
{
try
{
var pageSource = d.PageSource;
// 검색 결과나 "검색결과가 없습니다" 메시지가 나타나면 로드 완료
return pageSource.Contains("검색결과") ||
pageSource.Contains("총") ||
pageSource.Contains("자료가") ||
pageSource.Contains("bookList") ||
pageSource.Contains("searchResult");
}
catch
{
return false;
}
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(3000);
Console.WriteLine($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,346 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using UniMarc.;
using OpenQA.Selenium.Chromium;
using UniMarc.SearchModel;
using System.Runtime.CompilerServices;
using AR;
namespace BokBonCheck
{
public class GwangjuCityLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://citylib.gwangju.go.kr/main/bookSearch";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public GwangjuCityLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"광주시립({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
Console.WriteLine("GwangjuCityLibSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"GwangjuCityLibSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"GwangjuCityLibSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
try
{
// 도서관 선택
if (!string.IsNullOrEmpty(AreaCode))
{
var libSelect = wait.Until(d => d.FindElement(By.CssSelector("select[name='libCode']")));
var selectElement = new SelectElement(libSelect);
selectElement.SelectByValue(AreaCode);
Thread.Sleep(300);
Console.WriteLine($"{AreaCode} 도서관으로 선택됨");
}
else
{
Console.WriteLine("전체 도서관으로 검색");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 실패: {ex.Message}");
return false;
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
var cururl = _driver.Url;
if (cururl.Equals(SiteUrl) == false)
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색어 입력
try
{
var searchInput = wait.Until(d => d.FindElement(By.Id("bookSearchQuery")));
searchInput.Clear();
searchInput.SendKeys(searchTerm);
}
catch (Exception ex)
{
result.ErrorMessage = $"검색어입력실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색 버튼 클릭
try
{
var searchButton = wait.Until(d => d.FindElement(By.CssSelector("button.bookSearchBtn")));
searchButton.Click();
}
catch (Exception ex)
{
result.ErrorMessage = $"검색버튼클릭실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
// 1. 검색결과가 없는 경우 확인
try
{
var noResultElement = driver.FindElement(By.XPath("//p[contains(text(), '조회된 도서가 없습니다')]"));
if (noResultElement != null)
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
// 2. srch_info div에서 직접 추출 시도
try
{
var srchInfoElement = driver.FindElement(By.CssSelector("div.srch_info"));
if (srchInfoElement != null)
{
// span.heighlight에서 숫자 추출 시도
try
{
var highlightElement = srchInfoElement.FindElement(By.CssSelector("span.heighlight"));
if (highlightElement != null)
{
var countText = highlightElement.Text.Trim();
if (int.TryParse(countText, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
catch { }
// 전체 텍스트에서 정규식으로 추출
var srchInfoText = srchInfoElement.Text;
var match = Regex.Match(srchInfoText, @"검색결과\s*총\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"srch_info 요소 검색 중 오류: {ex.Message}");
}
// 3. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("조회된 도서가 없습니다") ||
pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기
var htmlPatterns = new[]
{
@"검색결과\s*총\s*<span[^>]*class=""heighlight""[^>]*>\s*(\d+)\s*</span>\s*건",
@"검색결과\s*총\s*(\d+)\s*건",
@"<span[^>]*class=""heighlight""[^>]*>\s*(\d+)\s*</span>",
@"총\s*(\d+)\s*건"
};
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
errmessage = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 검색 결과 페이지가 로드될 때까지 대기
wait.Until(d =>
{
try
{
var pageSource = d.PageSource;
// 검색 결과나 관련 요소가 나타나면 로드 완료
return pageSource.Contains("srch_info") ||
pageSource.Contains("검색결과") ||
pageSource.Contains("조회된 도서가 없습니다") ||
pageSource.Contains("총") ||
pageSource.Contains("건");
}
catch
{
return false;
}
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(3000);
Console.WriteLine($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,214 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Web;
using UniMarc.SearchModel;
using System.Text;
using OpenQA.Selenium.Support.UI;
namespace BokBonCheck
{
public class GwangjuDongguLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://lib.donggu.kr/BookSearch/detail";
public bool HttpApiMode { get; set; } = true;
public int No { get; set; }
private static readonly HttpClient _httpClient = new HttpClient()
{
DefaultRequestHeaders =
{
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
}
};
public GwangjuDongguLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"광주동구({areaName})";
}
public async Task StartDriver(bool showdriver = false)
{
// HTTP 클라이언트 사용으로 별도 드라이버 불필요
await Task.CompletedTask;
}
public void StopDriver()
{
// HTTP 클라이언트 사용으로 별도 정리 불필요
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 검색어 URL 인코딩
var encodedSearchTerm = HttpUtility.UrlEncode(searchTerm, Encoding.UTF8);
// 검색 URL 구성
var searchUrl = $"{SiteUrl}?queryTitle={encodedSearchTerm}&queryPublisher=&queryAuthor=";
// 도서관 코드가 있으면 추가
if (!string.IsNullOrEmpty(AreaCode))
{
searchUrl += $"&libCode%5B%5D={AreaCode}";
}
else
{
// 전체 도서관 선택 (모든 도서관 코드 추가)
var allLibCodes = new[] { "129231", "129003", "729079", "729072", "729073" };
foreach (var libCode in allLibCodes)
{
searchUrl += $"&libCode%5B%5D={libCode}";
}
}
Console.WriteLine($"광주동구 검색 URL: {searchUrl}");
// HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl))
{
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
throw new HttpRequestException($"HTTP {(int)response.StatusCode} {response.StatusCode}: {errorContent}");
}
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = $"검색성공({resultCount}권)";
}
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = $"검색 오류: {ex.Message}";
result.BookCount = 0;
Console.WriteLine($"광주동구 검색 오류: {ex.Message}");
}
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
{
errorMessage = string.Empty;
try
{
// 1. 검색 결과가 없는 경우 확인
if (htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다") ||
htmlContent.Contains("자료가 없습니다"))
{
errorMessage = "검색결과없음";
return 0;
}
// 2. 검색 결과 수량 추출: <p class="totalCount">전체 <strong>2</strong> 건</p>
var patterns = new[]
{
@"<p[^>]*class=""totalCount""[^>]*>전체\s*<strong>\s*(\d+)\s*</strong>\s*건</p>",
@"전체\s*<strong>\s*(\d+)\s*</strong>\s*건",
@"<strong>\s*(\d+)\s*</strong>\s*건",
@"총\s*(\d+)\s*건"
};
foreach (var pattern in patterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"광주동구 검색 결과: {count}건");
return count;
}
}
}
// 3. 더 자세한 패턴으로 시도 (줄바꿈 포함)
var multilinePatterns = new[]
{
@"전체\s*<strong>\s*\r?\n?\s*(\d+)\s*\r?\n?\s*</strong>\s*건",
@"<strong>\s*\r?\n?\s*(\d+)\s*\r?\n?\s*</strong>\s*건"
};
foreach (var pattern in multilinePatterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
Console.WriteLine($"광주동구 검색 결과: {count}건");
return count;
}
}
}
errorMessage = "검색결과 패턴을 찾을 수 없음";
Console.WriteLine("광주동구 검색결과 패턴을 찾을 수 없음");
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
return -1;
}
}
public Task WaitForPageChange(WebDriverWait wait)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,496 @@
using AR;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Chromium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Web.UI.WebControls;
using System.Xml.Linq;
using UniMarc.SearchModel;
using UniMarc.;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
namespace BokBonCheck
{
public class GwangjuSeoguLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public virtual string SiteUrl => "https://library.seogu.gwangju.kr/index.9is?contentUid=9be5df897834aa07017868116d3407de";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public GwangjuSeoguLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"광주서구구립({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
// 브라우저 배율을 80%로 설정 (브라우저가 표시되는 경우에만)
if (showdriver)
{
try
{
((IJavaScriptExecutor)_driver).ExecuteScript("document.body.style.zoom = '0.8';");
Console.WriteLine("브라우저 배율을 80%로 설정했습니다.");
}
catch (Exception zoomEx)
{
Console.WriteLine($"브라우저 배율 설정 실패: {zoomEx.Message}");
}
}
Console.WriteLine("GwangjuSeoguLibSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"GwangjuSeoguLibSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"GwangjuSeoguLibSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
try
{
Console.WriteLine("도서관 선택 과정 시작...");
// 1. 현재 체크된 상태 확인
var allCheckboxes = wait.Until(d => d.FindElements(By.CssSelector("ul.lib_list2 input[type='checkbox']")));
var checkedCheckboxes = allCheckboxes.Where(cb => cb.Selected).ToList();
Console.WriteLine($"현재 체크된 체크박스 개수: {checkedCheckboxes.Count}");
// 2. 지정한 도서관만 정확히 1개 체크되어 있는지 확인
bool isTargetOnlyChecked = false;
if (!string.IsNullOrEmpty(AreaCode))
{
if (checkedCheckboxes.Count == 1)
{
var onlyChecked = checkedCheckboxes[0];
var onlyCheckedValue = onlyChecked.GetAttribute("value");
if (onlyCheckedValue == AreaCode)
{
Console.WriteLine($"✓ {AreaCode} 도서관만 정확히 선택되어 있음 - 완료");
isTargetOnlyChecked = true;
}
}
}
// 3. 목표 상태가 아니면 모든 체크박스 해제 후 지정 도서관만 선택
if (!isTargetOnlyChecked)
{
Console.WriteLine("모든 체크박스 해제 후 지정 도서관만 선택 중...");
// 모든 체크박스를 해제
foreach (var checkbox in allCheckboxes)
{
try
{
if (checkbox.Selected)
{
var checkboxValue = checkbox.GetAttribute("value");
Console.WriteLine($"{checkboxValue} 도서관 체크 해제 중...");
SafeClick(checkbox);
Thread.Sleep(100);
}
}
catch (Exception ex)
{
Console.WriteLine($"체크박스 해제 중 오류: {ex.Message}");
}
}
// 4. 지정 도서관만 체크
if (!string.IsNullOrEmpty(AreaCode))
{
Console.WriteLine($"목표 도서관 '{AreaCode}' 선택 중...");
try
{
var targetCheckbox = wait.Until(d => d.FindElement(By.CssSelector($"ul.lib_list2 input[type='checkbox'][value='{AreaCode}']")));
if (!targetCheckbox.Selected)
{
Console.WriteLine($"{AreaCode} 도서관 체크 중...");
SafeClick(targetCheckbox);
Thread.Sleep(300);
}
// 최종 선택 상태 확인
if (targetCheckbox.Selected)
{
Console.WriteLine($"✓ {AreaCode} 도서관 선택 완료");
}
else
{
Console.WriteLine($"✗ {AreaCode} 도서관 선택 실패");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"목표 도서관 선택 실패: {ex.Message}");
return false;
}
}
else
{
Console.WriteLine("AreaCode가 비어있음 - 전체 도서관으로 검색");
// 모든 체크박스를 체크
foreach (var checkbox in allCheckboxes)
{
try
{
if (!checkbox.Selected)
{
SafeClick(checkbox);
Thread.Sleep(100);
}
}
catch (Exception ex)
{
Console.WriteLine($"전체 선택 중 오류: {ex.Message}");
}
}
}
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 실패: {ex.Message}");
return false;
}
}
protected void SafeClick(IWebElement element)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. 요소가 보이도록 스크롤 후 일반 클릭
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].scrollIntoView(true);", element);
Thread.Sleep(300);
element.Click();
}
catch
{
try
{
// 2. JavaScript로 클릭 시도
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].click();", element);
}
catch
{
try
{
// 3. Actions 클래스 사용
var actions = new Actions(_driver);
actions.MoveToElement(element).Click().Perform();
}
catch
{
try
{
// 4. 체크박스의 경우 직접 체크 상태 변경
if (element.TagName.ToLower() == "input" && element.GetAttribute("type") == "checkbox")
{
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].checked = !arguments[0].checked;", element);
}
else
{
// 일반 요소는 강제 클릭
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].dispatchEvent(new Event('click'));", element);
}
}
catch (Exception ex)
{
Console.WriteLine($"모든 클릭 방법 실패: {ex.Message}");
}
}
}
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
var cururl = _driver.Url;
if (cururl.Equals(SiteUrl) == false)
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 페이지 로드 후 브라우저 배율을 80%로 설정 (브라우저가 표시되는 경우에만)
try
{
if (_driver.Manage().Window.Size.Width > 0) // 브라우저가 표시되는 경우
{
((IJavaScriptExecutor)_driver).ExecuteScript("document.body.style.zoom = '0.8';");
Console.WriteLine("페이지 로드 후 브라우저 배율을 80%로 설정했습니다.");
}
}
catch (Exception zoomEx)
{
Console.WriteLine($"페이지 배율 설정 실패: {zoomEx.Message}");
}
// 도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색어 입력
try
{
var searchInput = wait.Until(d => d.FindElement(By.Id("searchWord")));
// 요소가 보이도록 스크롤
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].scrollIntoView(true);", searchInput);
Thread.Sleep(300);
// 기존 값 제거 후 검색어 입력
searchInput.Clear();
searchInput.SendKeys(searchTerm);
Console.WriteLine($"검색어 '{searchTerm}' 입력 완료");
}
catch (Exception ex)
{
result.ErrorMessage = $"검색어입력실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색 버튼 클릭
try
{
var searchButton = wait.Until(d => d.FindElement(By.CssSelector("button[name='seachBbsBt']")));
SafeClick(searchButton);
Console.WriteLine("검색 버튼 클릭 완료");
}
catch (Exception ex)
{
result.ErrorMessage = $"검색버튼클릭실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출 (페이징 포함)
var (resultCount, ermsg) = await ExtractBookCountWithPaging(_driver, searchTerm);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private async Task<(int count, string message)> ExtractBookCountWithPaging(IWebDriver driver, string searchTerm)
{
string errmessage = string.Empty;
int totalCount = 0;
try
{
// 첫 번째 페이지에서 테이블 row 수 확인
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
// 검색 결과 테이블이 있는지 확인
try
{
var bookTable = wait.Until(d => d.FindElement(By.CssSelector("table.board_list tbody#bookList")));
var firstPageRows = bookTable.FindElements(By.TagName("tr"));
if (firstPageRows.Count == 0)
{
errmessage = "검색결과없음";
return (0, errmessage);
}
totalCount += firstPageRows.Count;
Console.WriteLine($"첫 번째 페이지 결과: {firstPageRows.Count}건");
}
catch
{
errmessage = "검색결과없음";
return (0, errmessage);
}
// 페이징이 있는지 확인하고 각 페이지 방문
try
{
var paginationDiv = driver.FindElement(By.CssSelector("div.pagination"));
var pageLinks = paginationDiv.FindElements(By.TagName("a")).Where(a =>
!a.GetAttribute("class").Contains("select") && // 현재 페이지가 아닌 것만
!string.IsNullOrEmpty(a.Text.Trim()) &&
int.TryParse(a.Text.Trim(), out int pageNum) // 숫자인 것만
).ToList();
Console.WriteLine($"추가 페이지 {pageLinks.Count}개 발견");
foreach (var pageLink in pageLinks)
{
try
{
var pageNumber = pageLink.Text.Trim();
Console.WriteLine($"{pageNumber} 페이지로 이동 중...");
SafeClick(pageLink);
await Task.Delay(2000); // 페이지 로딩 대기
// 새 페이지에서 결과 수 확인
var newBookTable = wait.Until(d => d.FindElement(By.CssSelector("table.board_list tbody#bookList")));
var pageRows = newBookTable.FindElements(By.TagName("tr"));
totalCount += pageRows.Count;
Console.WriteLine($"{pageNumber} 페이지 결과: {pageRows.Count}건");
}
catch (Exception ex)
{
Console.WriteLine($"페이지 이동 중 오류: {ex.Message}");
}
}
}
catch
{
Console.WriteLine("페이징이 없거나 페이징 처리 중 오류 발생");
}
if (totalCount == 0)
{
errmessage = "검색결과없음";
return (0, errmessage);
}
errmessage = $"검색성공({totalCount}권)";
Console.WriteLine($"전체 검색 결과: {totalCount}건");
return (totalCount, errmessage);
}
catch (Exception ex)
{
errmessage = ex.Message;
return (-1, errmessage);
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 검색 결과 페이지가 로드될 때까지 대기
wait.Until(d =>
{
try
{
var pageSource = d.PageSource;
// 검색 결과나 관련 요소가 나타나면 로드 완료
return pageSource.Contains("board_list") ||
pageSource.Contains("bookList") ||
pageSource.Contains("검색결과가 없습니다");
}
catch
{
return false;
}
});
await Task.Delay(500);
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(3000);
Console.WriteLine($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,15 @@
using BokBonCheck;
namespace BokBonCheck
{
public class GwangjuSeoguSmallLibSearcher : GwangjuSeoguLibSearcher
{
public override string SiteUrl => "https://library.seogu.gwangju.kr/library/index.9is?contentUid=9be5df8990e39d0e019491d3c5455c5c";
public GwangjuSeoguSmallLibSearcher(int no, string areaCode, string areaName)
: base(no, areaCode, areaName)
{
this.SiteName = $"광주서구구립-작은도서관({areaName})";
}
}
}

View File

@@ -0,0 +1,15 @@
using BokBonCheck;
namespace BokBonCheck
{
public class GwangjuSeoguSmartLibSearcher : GwangjuSeoguLibSearcher
{
public override string SiteUrl => "https://library.seogu.gwangju.kr/library/index.9is?contentUid=9be5df897834aa08017868afdc3c1820";
public GwangjuSeoguSmartLibSearcher(int no, string areaCode, string areaName)
: base(no, areaCode, areaName)
{
this.SiteName = $"광주서구구립-스마트도서관({areaName})";
}
}
}

View File

@@ -0,0 +1,197 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Web;
using UniMarc.SearchModel;
using System.Text;
namespace BokBonCheck
{
public class GwangsanLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://lib.gwangsan.go.kr/main/bookSearch/advanced";
public bool HttpApiMode { get; set; } = true;
public int No { get; set; }
private static readonly HttpClient _httpClient = new HttpClient()
{
DefaultRequestHeaders =
{
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
}
};
public GwangsanLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"광주광산구({areaName})";
}
public async Task StartDriver(bool showdriver = false)
{
// HTTP 클라이언트 사용으로 별도 드라이버 불필요
await Task.CompletedTask;
}
public void StopDriver()
{
// HTTP 클라이언트 사용으로 별도 정리 불필요
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 검색어 URL 인코딩
var encodedSearchTerm = HttpUtility.UrlEncode(searchTerm, Encoding.UTF8);
// 상세검색 URL 구성 (도서명만 검색)
var searchUrl = $"{SiteUrl}?query={encodedSearchTerm}&queryPublisher=&queryAuthor=&queryPubYearFr=&queryPubYearTo=";
// 도서관 코드가 있으면 추가
if (!string.IsNullOrEmpty(AreaCode))
{
searchUrl += $"&libCodeArr={AreaCode}";
}
Console.WriteLine($"광주광산구 검색 URL: {searchUrl}");
// HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl))
{
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = $"검색성공({resultCount}권)";
}
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = $"검색 오류: {ex.Message}";
result.BookCount = 0;
Console.WriteLine($"광주광산구 검색 오류: {ex.Message}");
}
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
{
errorMessage = string.Empty;
try
{
// 검색 결과가 없는 경우 확인
if (htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다") ||
htmlContent.Contains("자료가 없습니다"))
{
errorMessage = "검색결과없음";
return 0;
}
// HTML에서 "전체 <strong>N</strong> 건" 패턴 찾기
var patterns = new[]
{
@"전체\s*<strong>\s*(\d+)\s*</strong>\s*건",
@"전체\s+(\d+)\s+건",
@"총\s*(\d+)\s*권",
@"총\s*(\d+)\s*건"
};
foreach (var pattern in patterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
// 더 자세한 패턴으로 시도 (줄바꿈 포함)
var multilinePatterns = new[]
{
@"전체\s*<strong>\s*\r?\n?\s*(\d+)\s*\r?\n?\s*</strong>\s*건",
@"<strong>\s*\r?\n?\s*(\d+)\s*\r?\n?\s*</strong>\s*건"
};
foreach (var pattern in multilinePatterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
return -1;
}
}
public Task WaitForPageChange(OpenQA.Selenium.Support.UI.WebDriverWait wait)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,306 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Web;
using UniMarc.SearchModel;
using System.Text;
using OpenQA.Selenium.Support.UI;
using OpenQA.Selenium;
using OpenQA.Selenium.Chromium;
using System.Threading;
namespace BokBonCheck
{
public class GyeongnamLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://lib.gyeongnam.go.kr/index.lib?menuCd=DOM_000000201012000000";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public GyeongnamLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"경남대표도서관({areaName})";
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
Console.WriteLine("GyeongnamLibSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"GyeongnamLibSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"GyeongnamLibSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
// 검색어 URL 인코딩
var encodedSearchTerm = HttpUtility.UrlEncode(searchTerm, Encoding.UTF8);
// 직접 검색 URL 구성해서 이동
var directSearchUrl = $"https://lib.gyeongnam.go.kr/index.lib?menuCd=DOM_000000201001014000&search_select=search_title&search_text={encodedSearchTerm}";
Console.WriteLine($"직접 검색 URL로 이동: {directSearchUrl}");
_driver.Navigate().GoToUrl(directSearchUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 완전한 페이지 로딩 대기
await WaitForCompletePageLoad(wait);
// 페이지 로드 후 브라우저 배율을 80%로 설정
try
{
if (_driver.Manage().Window.Size.Width > 0)
{
((IJavaScriptExecutor)_driver).ExecuteScript("document.body.style.zoom = '0.8';");
Console.WriteLine("페이지 로드 후 브라우저 배율을 80%로 설정했습니다.");
}
}
catch (Exception zoomEx)
{
Console.WriteLine($"페이지 배율 설정 실패: {zoomEx.Message}");
}
// JavaScript 렌더링 대기
await Task.Delay(3000);
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
// JavaScript 실행 후 실제 렌더링된 DOM에서 결과 추출
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
// 1. 검색결과가 없는 경우 확인
try
{
var noResultElements = driver.FindElements(By.XPath("//*[contains(text(), '검색결과가 없습니다') or contains(text(), '검색된 자료가 없습니다')]"));
if (noResultElements.Count > 0)
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
// 2. total_area에서 결과 수량 추출 (JavaScript 렌더링 후)
try
{
var totalAreaElement = wait.Until(d => d.FindElement(By.CssSelector("div.total_area p.total span")));
if (totalAreaElement != null)
{
var countText = totalAreaElement.Text.Trim();
Console.WriteLine($"total_area 텍스트: '{countText}'");
// "총 3건" 형태에서 숫자 추출
var match = Regex.Match(countText, @"총\s*(\d+)\s*건", RegexOptions.IgnoreCase);
if (match.Success && int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
Console.WriteLine("검색 결과: 0건");
return 0;
}
errmessage = $"검색성공({count}권)";
Console.WriteLine($"검색 결과: {count}건");
return count;
}
}
}
catch (Exception ex1)
{
Console.WriteLine($"total_area 요소 추출 실패: {ex1.Message}");
}
// 3. 페이지 소스에서 렌더링된 결과 추출
var pageSource = driver.PageSource;
Console.WriteLine("페이지 소스에서 결과 추출 시도");
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다") ||
pageSource.Contains("자료가 없습니다") ||
pageSource.Contains("총 0건"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기 (렌더링된 결과)
var htmlPatterns = new[]
{
@"<span>총\s*(\d+)\s*건</span>",
@"총\s*(\d+)\s*건이\s*검색되었습니다",
@"총\s*(\d+)\s*건",
@"검색결과\s*:\s*(\d+)\s*건"
};
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
errmessage = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 완전한 페이지 로딩 대기 메서드
private async Task WaitForCompletePageLoad(WebDriverWait wait)
{
try
{
Console.WriteLine("완전한 페이지 로딩 대기 시작...");
// 1. document.readyState가 'complete'가 될 때까지 대기
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
Console.WriteLine("document.readyState = complete");
// 2. jQuery가 로드되고 ready 상태까지 대기 (만약 사용한다면)
try
{
wait.Until(d =>
{
var jqueryReady = ((IJavaScriptExecutor)d).ExecuteScript("return typeof jQuery !== 'undefined' && jQuery.active == 0");
return jqueryReady.Equals(true);
});
Console.WriteLine("jQuery ready 완료");
}
catch
{
Console.WriteLine("jQuery 없음 또는 대기 생략");
}
// 3. 추가 대기 시간
await Task.Delay(2000);
// 4. 검색 입력창이 실제로 존재하고 상호작용 가능할 때까지 대기
wait.Until(d =>
{
try
{
var searchInput = d.FindElement(By.Id("search_text"));
return searchInput != null && searchInput.Displayed && searchInput.Enabled;
}
catch
{
return false;
}
});
Console.WriteLine("검색 입력창 준비 완료");
}
catch (Exception ex)
{
Console.WriteLine($"페이지 로딩 대기 중 오류: {ex.Message}");
// 오류가 발생해도 최소한의 대기 시간 적용
await Task.Delay(3000);
}
}
public Task WaitForPageChange(WebDriverWait wait)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,27 @@
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System.IO;
using System;
using System.Threading.Tasks;
using Org.BouncyCastle.Bcpg;
namespace BokBonCheck
{
public interface ILibrarySearcher
{
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ũ<>Ѹ<EFBFBD><D1B8><EFBFBD> <20>ƴ<EFBFBD> HTTP ȣ<><C8A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ó<><C3B3><EFBFBD>˴ϴ<CBB4>
/// </summary>
bool HttpApiMode { get; set; }
int No { get; set; }
string SiteName { get; }
string SiteUrl { get; }
Task<BookSearchResult> SearchAsync(string searchTerm);
Task StartDriver(bool showBrowser);
void StopDriver();
Task WaitForPageChange(WebDriverWait wait);
}
}

View File

@@ -0,0 +1,181 @@
using System;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Net.Http;
using System.Collections.Generic; // Added missing import
using OpenQA.Selenium.Support.UI; // Added missing import
namespace BokBonCheck
{
public class IksanLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://lib.iksan.go.kr/main/site/search/bookSearch.do";
public bool HttpApiMode { get; set; } = true;
public int No { get; set; }
private static readonly HttpClient _httpClient = new HttpClient()
{
DefaultRequestHeaders =
{
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
}
};
public IksanLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"익산시립({areaName})";
}
public void StopDriver()
{
// HTTP 클라이언트 사용으로 별도 정리 불필요
}
public async Task StartDriver(bool showdriver = false)
{
// HTTP 클라이언트 사용으로 별도 드라이버 불필요
await Task.CompletedTask;
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 검색어 URL 인코딩
var encodedSearchTerm = System.Web.HttpUtility.UrlEncode(searchTerm, System.Text.Encoding.UTF8);
// 검색 URL 구성 - GET 방식으로 URL 파라미터 전송
var searchUrl = $"{SiteUrl}?cmd_name=bookandnonbooksearch&search_type=detail&detail=OK&use_facet=N&all_lib=N&search_item=search_title&search_txt=&search_title={encodedSearchTerm}";
// 도서관 코드가 있으면 추가
if (!string.IsNullOrEmpty(AreaCode))
{
searchUrl += $"&manage_code={AreaCode}";
}
Console.WriteLine($"익산시통합도서관 검색 요청: {searchTerm}, 도서관코드: {AreaCode}");
Console.WriteLine($"검색 URL: {searchUrl}");
// HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl))
{
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = $"검색성공({resultCount}권)";
}
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(string htmlContent, out string errmessage)
{
errmessage = string.Empty;
try
{
// 실제 HTML 구조에 맞는 패턴으로 수정
var htmlPatterns = new[]
{
// "상세검색결과 <strong>3</strong>개" 패턴 (실제 구조)
@"상세검색결과\s*<strong[^>]*>\s*(\d+)\s*</strong>\s*개",
// "전체 <strong class="word">1</strong>개가 검색되었습니다" 패턴 (이전 패턴)
@"전체\s*<strong[^>]*class=""word""[^>]*>\s*(\d+)\s*</strong>\s*개가\s*검색되었습니다",
// "전체 <strong class="word">0</strong>개가 검색되었습니다" 패턴 (0인 경우)
@"전체\s*<strong[^>]*class=""word""[^>]*>\s*0\s*</strong>\s*개가\s*검색되었습니다",
// 일반적인 "전체 X개가 검색되었습니다" 패턴
@"전체\s*(\d+)\s*개가\s*검색되었습니다",
// 숫자만 찾는 패턴 (마지막 수단)
@"(\d+)\s*개가\s*검색되었습니다"
};
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
// 0인 경우를 먼저 확인
if (pattern.Contains(@"\s*0\s*"))
{
errmessage = "검색결과없음";
return 0;
}
// 숫자 추출
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
// 디버깅을 위해 HTML 내용 일부 출력
Console.WriteLine($"HTML 내용 일부: {htmlContent.Substring(0, Math.Min(1000, htmlContent.Length))}");
errmessage = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드 (HTTP 방식에서는 불필요)
public async Task WaitForPageChange(WebDriverWait wait)
{
// HTTP 방식에서는 즉시 응답이 오므로 대기 불필요
await Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,404 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using UniMarc.;
using OpenQA.Selenium.Chromium;
using UniMarc.SearchModel;
using System.Runtime.CompilerServices;
using AR;
namespace BokBonCheck
{
public class JeonbukEduLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://lib.jbe.go.kr/jbe/intro/search/index.do";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public JeonbukEduLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"전북교육청({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
Console.WriteLine("JeonbukEduLibSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"JeonbukEduLibSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"JeonbukEduLibSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
try
{
// 전체 선택인 경우 - 모든 체크박스 선택
if (string.IsNullOrEmpty(AreaCode) || AreaCode == "ALL")
{
var allCheckboxes1 = wait.Until(d => d.FindElements(By.CssSelector("input[name='libraryCodes']")));
foreach (var checkbox in allCheckboxes1)
{
if (!checkbox.Selected)
{
SafeClick(checkbox);
Thread.Sleep(100);
}
}
Console.WriteLine("전체 도서관 선택됨");
return true;
}
// 특정 도서관 선택인 경우
var targetCheckbox = wait.Until(d => d.FindElement(By.CssSelector($"input[name='libraryCodes'][value='{AreaCode}']")));
if (targetCheckbox == null)
{
Console.WriteLine($"도서관 체크박스를 찾을 수 없습니다: {AreaCode}");
return false;
}
// 다른 체크박스들 해제
var allCheckboxes = wait.Until(d => d.FindElements(By.CssSelector("input[name='libraryCodes']")));
foreach (var checkbox in allCheckboxes)
{
if (checkbox.GetAttribute("value") != AreaCode && checkbox.Selected)
{
SafeClick(checkbox);
Thread.Sleep(100);
}
}
// 대상 체크박스 선택
if (!targetCheckbox.Selected)
{
SafeClick(targetCheckbox);
Thread.Sleep(300);
Console.WriteLine($"{AreaCode} 도서관으로 변경됨");
}
else
{
Console.WriteLine($"{AreaCode} 도서관이 이미 선택되어 있음");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 실패: {ex.Message}");
return false;
}
}
protected void SafeClick(IWebElement element)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. JavaScript로 클릭 시도
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click();", element);
}
catch
{
try
{
// 2. 요소가 보이도록 스크롤 후 클릭
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", element);
Thread.Sleep(500);
element.Click();
}
catch
{
try
{
// 3. Actions 클래스 사용
var driver = ((IWrapsDriver)element).WrappedDriver;
var actions = new OpenQA.Selenium.Interactions.Actions(driver);
actions.MoveToElement(element).Click().Perform();
}
catch
{
// 4. 마지막 방법: JavaScript로 직접 체크 상태 변경
var driver = ((IWrapsDriver)element).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].checked = !arguments[0].checked;", element);
}
}
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
var cururl = _driver.Url;
if (cururl.Equals(SiteUrl) == false)
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색어 입력
try
{
var searchInput = wait.Until(d => d.FindElement(By.Id("search_text")));
searchInput.Clear();
searchInput.SendKeys(searchTerm);
}
catch (Exception ex)
{
result.ErrorMessage = $"검색어입력실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색 버튼 클릭
try
{
var searchButton = wait.Until(d => d.FindElement(By.Id("do-search")));
searchButton.Click();
}
catch (Exception ex)
{
result.ErrorMessage = $"검색버튼클릭실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
// 1. search-info div에서 직접 추출 시도
try
{
var searchInfoElement = driver.FindElement(By.CssSelector("div.search-info"));
if (searchInfoElement != null)
{
var searchInfoText = searchInfoElement.Text;
// "총 N건이 검색되었습니다" 패턴 찾기
var match = Regex.Match(searchInfoText, @"총\s*(\d+)\s*건이\s*검색되었습니다", RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
// <b> 태그에서 직접 숫자 추출 시도
try
{
var boldElements = searchInfoElement.FindElements(By.TagName("b"));
foreach (var boldElement in boldElements)
{
var boldText = boldElement.Text.Trim();
if (int.TryParse(boldText, out int boldCount))
{
if (boldCount == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({boldCount}권)";
return boldCount;
}
}
}
catch { }
}
}
catch (Exception ex)
{
Console.WriteLine($"search-info 요소 검색 중 오류: {ex.Message}");
}
// 2. 페이지 소스에서 정규식으로 추출 시도
var pageSource = driver.PageSource;
// 검색 결과가 없다는 메시지 확인
if (pageSource.Contains("0건이 검색되었습니다") ||
pageSource.Contains("검색결과가 없습니다") ||
pageSource.Contains("검색된 자료가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기
var htmlPatterns = new[]
{
@"총\s*<b>(\d+)</b>\s*건이\s*검색되었습니다",
@"총\s*(\d+)\s*건이\s*검색되었습니다",
@"총\s*<b>\s*(\d+)\s*</b>\s*건",
@"총\s*(\d+)\s*건"
};
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(pageSource, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
errmessage = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 검색 결과 페이지가 로드될 때까지 대기
wait.Until(d =>
{
try
{
var pageSource = d.PageSource;
// search-info div나 검색 결과 관련 요소가 나타나면 로드 완료
return pageSource.Contains("search-info") ||
pageSource.Contains("검색되었습니다") ||
pageSource.Contains("검색결과") ||
pageSource.Contains("총") ||
pageSource.Contains("건이");
}
catch
{
return false;
}
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(3000);
Console.WriteLine($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,413 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using UniMarc.;
using OpenQA.Selenium.Chromium;
using UniMarc.SearchModel;
using System.Runtime.CompilerServices;
using AR;
namespace BokBonCheck
{
public class JunnamEduJiheaNuriSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://jnelib.jne.go.kr/book/search_book/search.es?mid=d20101000000";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public JunnamEduJiheaNuriSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"전남교육청행정자료실({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
Console.WriteLine("JunnamEduJiheaNuriSearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"JunnamEduJiheaNuriSearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"JunnamEduJiheaNuriSearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
try
{
if (this.AreaCode.isEmpty()) return true;
// Areacode가 "ALL"인 경우
if (AreaCode == "ALL")
{
var allCheckbox = wait.Until(d => d.FindElement(By.Id("all_srch")));
if (!allCheckbox.Selected)
{
SafeClick(allCheckbox);
Thread.Sleep(300);
Console.WriteLine("전체 선택으로 변경됨");
}
else
{
Console.WriteLine("전체 선택이 이미 활성화되어 있음");
}
return true;
}
// 특정 지역 선택인 경우
var targetSelector = $"libInfo_{AreaCode}";
var targetCheckbox = wait.Until(d => d.FindElement(By.Id(targetSelector)));
if (targetCheckbox == null)
{
Console.WriteLine($"도서관 체크박스를 찾을 수 없습니다: {targetSelector}");
return false;
}
// 전체 선택이 되어 있으면 해제
var allCheckbox2 = wait.Until(d => d.FindElement(By.Id("all_srch")));
if (allCheckbox2.Selected)
{
SafeClick(allCheckbox2);
Thread.Sleep(300);
Console.WriteLine("전체 선택 해제됨");
}
// 대상 지역 선택
if (!targetCheckbox.Selected)
{
SafeClick(targetCheckbox);
Thread.Sleep(300);
Console.WriteLine($"{AreaCode} 지역으로 변경됨");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 실패: {ex.Message}");
return false;
}
}
protected void SafeClick(IWebElement searchBox)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. JavaScript로 클릭 시도
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click();", searchBox);
}
catch
{
try
{
// 2. 요소가 보이도록 스크롤 후 클릭
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", searchBox);
Thread.Sleep(500);
searchBox.Click();
}
catch
{
try
{
// 3. Actions 클래스 사용
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
var actions = new OpenQA.Selenium.Interactions.Actions(driver);
actions.MoveToElement(searchBox).Click().Perform();
}
catch
{
// 4. 마지막 방법: JavaScript로 직접 체크 해제
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].checked = false;", searchBox);
}
}
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
var cururl = _driver.Url;
if (cururl.Equals(SiteUrl) == false)
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색 방법을 서명으로 변경
try
{
var searchKeyWordSelect = wait.Until(d => d.FindElement(By.Id("searchKeyWord")));
var selectElement = new SelectElement(searchKeyWordSelect);
selectElement.SelectByValue("1"); // 1 = 서명
Thread.Sleep(300);
}
catch (Exception ex)
{
result.ErrorMessage = $"검색방법선택실패({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색어 입력
IWebElement searchBox = null;
try
{
searchBox = wait.Until(d => d.FindElement(By.Id("searchWordText")));
searchBox.Clear();
searchBox.SendKeys(searchTerm);
}
catch (Exception ex)
{
result.ErrorMessage = $"검색창없음({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색 버튼 클릭
IWebElement searchButton = null;
try
{
searchButton = _driver.FindElement(By.CssSelector("button[type='submit']"));
if (searchButton != null)
searchButton.Click();
else
{
result.ErrorMessage = $"검색버튼없음";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
}
catch (Exception ex)
{
result.ErrorMessage = $"검색버튼없음({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
// 먼저 검색결과가 없는 경우 확인
try
{
var noResultDiv = driver.FindElement(By.CssSelector(".SearchResult .resultBook"));
var noResultText = noResultDiv.Text;
if (noResultText.Contains("검색결과가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
// 검색결과가 없는 경우 div가 없을 수도 있음
}
// 검색결과가 있는 경우 p.page_info에서 전체 건수 추출
try
{
var pageInfo = driver.FindElement(By.CssSelector("p.page_info"));
var pageInfoText = pageInfo.Text; // 예: "전체 7건, 현재 페이지 1/1"
var match = System.Text.RegularExpressions.Regex.Match(pageInfoText, @"전체\s*(\d+)건");
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int vqty))
{
errmessage = $"검색성공({vqty}건)";
return vqty;
}
else
{
errmessage = $"수량값오류({match.Groups[1].Value})";
return -1;
}
}
else
{
errmessage = "수량항목없음";
return -1;
}
}
catch (Exception ex)
{
// page_info가 없는 경우 검색결과가 없는 것으로 판단
errmessage = "검색결과없음";
return 0;
}
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 검색 결과 페이지가 로드될 때까지 대기
wait.Until(d =>
{
try
{
// 검색결과가 없는 경우 확인
try
{
var noResultDiv = d.FindElement(By.CssSelector(".SearchResult .resultBook"));
if (noResultDiv.Text.Contains("검색결과가 없습니다"))
{
return true; // 검색결과 없음 페이지 로드 완료
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
// 검색결과가 있는 경우 page_info 확인
try
{
var pageInfo = d.FindElement(By.CssSelector("p.page_info"));
var pageInfoText = pageInfo.Text;
// "전체 N건" 형식이 나타나면 로드 완료
return pageInfoText.Contains("전체") && pageInfoText.Contains("건");
}
catch
{
return false;
}
}
catch
{
return false;
}
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(2000);
throw new Exception($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,425 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using UniMarc.;
using OpenQA.Selenium.Chromium;
using UniMarc.SearchModel;
using System.Runtime.CompilerServices;
namespace BokBonCheck
{
public class JunnamEduSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://jnelib.jne.go.kr/book/search_book/search.es?mid=d50101000000";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
public JunnamEduSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"전남교육청({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser: showdriver);
Console.WriteLine("NamguLibrarySearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"NamguLibrarySearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"NamguLibrarySearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
try
{
// Areacode가 "ALL"인 경우
if (AreaCode == "ALL")
{
var allCheckbox1 = wait.Until(d => d.FindElement(By.CssSelector("#all_srch")));
// 이미 전체가 선택되어 있으면 변경하지 않음
if (allCheckbox1.Selected)
{
Console.WriteLine("전체 선택이 이미 활성화되어 있음");
return true;
}
// 전체 선택이 안되어 있으면 활성화
SafeClick(allCheckbox1);
Thread.Sleep(300);
Console.WriteLine("전체 선택으로 변경됨");
return true;
}
// 특정 지역 선택인 경우
var targetSelector = $"#libInfo_{AreaCode}";
var targetCheckbox = wait.Until(d => d.FindElement(By.CssSelector(targetSelector)));
if (targetCheckbox == null)
{
Console.WriteLine($"도서관 체크박스를 찾을 수 없습니다: {targetSelector}");
return false;
}
// 현재 상태 확인
var allCheckbox = wait.Until(d => d.FindElement(By.CssSelector("#all_srch")));
bool isAllSelected = allCheckbox.Selected;
bool isTargetSelected = targetCheckbox.Selected;
// 이미 원하는 지역이 선택되어 있고, 전체 선택이 해제되어 있으면 변경하지 않음
if (isTargetSelected && !isAllSelected)
{
Console.WriteLine($"{AreaCode} 지역이 이미 선택되어 있음");
return true;
}
// 전체 선택이 되어 있으면 해제
if (isAllSelected)
{
SafeClick(allCheckbox);
Thread.Sleep(300);
Console.WriteLine("전체 선택 해제됨");
}
// 대상 지역이 선택되어 있지 않으면 선택
if (!targetCheckbox.Selected)
{
SafeClick(targetCheckbox);
Thread.Sleep(300);
Console.WriteLine($"{AreaCode} 지역으로 변경됨");
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"도서관 선택 실패: {ex.Message}");
return false;
}
}
protected void SafeClick(IWebElement searchBox)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. JavaScript로 클릭 시도
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click();", searchBox);
}
catch
{
try
{
// 2. 요소가 보이도록 스크롤 후 클릭
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", searchBox);
Thread.Sleep(500);
searchBox.Click();
}
catch
{
try
{
// 3. Actions 클래스 사용
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
var actions = new OpenQA.Selenium.Interactions.Actions(driver);
actions.MoveToElement(searchBox).Click().Perform();
}
catch
{
// 4. 마지막 방법: JavaScript로 직접 체크 해제
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].checked = false;", searchBox);
}
}
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
var cururl = _driver.Url;
if (cururl.Equals(SiteUrl) == false)
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 모든 감지 방법이 끝나면 크롬창 최소화
// whale 브라우저가 최소화되어 우선해제
//IntPtr chromeWindow = FindWindow("Chrome_WidgetWin_1", null);
//if (chromeWindow != IntPtr.Zero)
//{
// ShowWindow(chromeWindow, SW_MINIMIZE);
//}
//대상도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색창 찾기 (남구통합도서관 사이트의 특정 선택자 사용)
IWebElement searchBox = null;
try
{
try
{
searchBox = wait.Until(d => d.FindElement(By.CssSelector("#searchWordText")));
}
catch (Exception ex)
{
result.ErrorMessage = $"검색창없음({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
}
catch (Exception ex)
{
throw new Exception($"검색창 찾기 실패: {ex.Message}");
}
// 검색어 입력
searchBox.Clear();
searchBox.SendKeys(searchTerm);
// 검색 버튼 클릭
IWebElement searchButton = null;
try
{
searchButton = _driver.FindElement(By.CssSelector("button[type='submit']"));
if (searchButton != null)
searchButton.Click();
else
{
result.ErrorMessage = $"검색버튼없음";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
}
catch (Exception ex)
{
result.ErrorMessage = $"검색버튼없음({ex.Message})";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
// 먼저 검색결과가 없는 경우 확인
try
{
var noResultDiv = driver.FindElement(By.CssSelector(".SearchResult .resultBook"));
var noResultText = noResultDiv.Text;
if (noResultText.Contains("검색결과가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
}
}
catch
{
// 검색결과가 없는 경우 div가 없을 수도 있음
}
// 검색결과가 있는 경우 p.page_info에서 전체 건수 추출
try
{
var pageInfo = driver.FindElement(By.CssSelector("p.page_info"));
var pageInfoText = pageInfo.Text; // 예: "전체 7건, 현재 페이지 1/1"
var match = System.Text.RegularExpressions.Regex.Match(pageInfoText, @"전체\s*(\d+)건");
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int vqty))
{
errmessage = $"검색성공({vqty}건)";
return vqty;
}
else
{
errmessage = $"수량값오류({match.Groups[1].Value})";
return -1;
}
}
else
{
errmessage = "수량항목없음";
return -1;
}
}
catch (Exception ex)
{
// page_info가 없는 경우 검색결과가 없는 것으로 판단
errmessage = "검색결과없음";
return 0;
}
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 검색 결과 페이지가 로드될 때까지 대기
wait.Until(d =>
{
try
{
// 검색결과가 없는 경우 확인
try
{
var noResultDiv = d.FindElement(By.CssSelector(".SearchResult .resultBook"));
if (noResultDiv.Text.Contains("검색결과가 없습니다"))
{
return true; // 검색결과 없음 페이지 로드 완료
}
}
catch
{
// 검색결과가 있는 경우로 진행
}
// 검색결과가 있는 경우 page_info 확인
try
{
var pageInfo = d.FindElement(By.CssSelector("p.page_info"));
var pageInfoText = pageInfo.Text;
// "전체 N건" 형식이 나타나면 로드 완료
return pageInfoText.Contains("전체") && pageInfoText.Contains("건");
}
catch
{
return false;
}
}
catch
{
return false;
}
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(2000);
throw new Exception($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,261 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using UniMarc.SearchModel;
using OpenQA.Selenium.Chromium;
namespace BokBonCheck
{
public class KwangjuCityEduLibrarySearcher : ILibrarySearcher
{
public int No { get; set; }
protected string AreaCode = "";
public string SiteName { get; protected set; } = "광주시교육청통합도서관";
public string SiteUrl => "https://lib.gen.go.kr/main/site/search/bookSearch.do#simple";
public bool HttpApiMode { get; set; } = false;
private ChromiumDriver _driver;
public KwangjuCityEduLibrarySearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"광주시교육청통합도서관({areaName})";
}
public async Task StartDriver(bool showBrowser = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser:showBrowser);
Console.WriteLine("KwangjuCityLibrarySearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"KwangjuCityLibrarySearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"KwangjuCityLibrarySearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
virtual protected void SelectLibrary(WebDriverWait wait)
{
try
{
// 콤보박스(select) 요소 찾기
var selectElement = wait.Until(d => d.FindElement(By.CssSelector("#manage_code")));
var select = new OpenQA.Selenium.Support.UI.SelectElement(selectElement);
// value가 "MA"인 옵션 선택
select.SelectByValue(AreaCode);
}
catch
{
// 예외 처리 (필요시 로깅 등)
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
if (_driver == null)
throw new InvalidOperationException("드라이버가 시작되지 않았습니다. StartDriver()를 먼저 호출하세요.");
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 모든 감지 방법이 끝나면 크롬창 최소화
// whale 브라우저가 최소화되어 우선해제
//IntPtr chromeWindow = FindWindow("Chrome_WidgetWin_1", null);
//if (chromeWindow != IntPtr.Zero)
//{
// ShowWindow(chromeWindow, SW_MINIMIZE);
//}
//대상도서관 선택
SelectLibrary(wait);
// 검색창 찾기
IWebElement searchBox = null;
try
{
// 여러 가능한 선택자 시도
var selectors = new[]
{
"input[id='search_txt']",
"input[type='text']",
};
foreach (var selector in selectors)
{
try
{
searchBox = wait.Until(d =>
{
var el = d.FindElement(By.CssSelector(selector));
return (el != null && el.Displayed && el.Enabled) ? el : null;
});
break;
}
catch
{
continue;
}
}
if (searchBox == null)
{
throw new Exception("검색창을 찾을 수 없습니다.");
}
}
catch (Exception ex)
{
throw new Exception($"검색창 찾기 실패: {ex.Message}");
}
// 혹시 readonly라면 속성 제거
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].removeAttribute('readonly')", searchBox);
// 입력 전 clear
searchBox.Clear();
searchBox.SendKeys(searchTerm);
// 검색 버튼 클릭
IWebElement searchButton = null;
try
{
var buttonSelectors = new[]
{
"input[type='submit']",
};
foreach (var selector in buttonSelectors)
{
try
{
searchButton = _driver.FindElement(By.CssSelector(selector));
break;
}
catch
{
continue;
}
}
if (searchButton == null)
{
// Enter 키로 검색 시도
searchBox.SendKeys(Keys.Enter);
}
else
{
searchButton.Click();
}
}
catch (Exception ex)
{
// Enter 키로 검색 시도
searchBox.SendKeys(Keys.Enter);
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver);
result.BookCount = resultCount;
result.IsSuccess = true;
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver)
{
try
{
// div.search-result 내부의 span에서 '전체 N' 텍스트 추출
var resultDiv = driver.FindElement(By.CssSelector("div.ndls_result"));
var span = resultDiv.FindElement(By.XPath(".//span[contains(text(),'전체')]"));
string text = span.Text; // 예: "전체 5 "
var match = System.Text.RegularExpressions.Regex.Match(text, @"전체\s*(\d+)");
if (match.Success)
{
return int.Parse(match.Groups[1].Value);
}
return 0;
}
catch
{
return 0;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
// 방법 4: 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 방법 5: 특정 텍스트가 페이지에 나타날 때까지 대기
wait.Until(d =>
{
var elm = d.FindElement(By.TagName("body"));
if (elm == null) return false;
var pageText = elm.Text;
return pageText.Contains("전체") || pageText.Contains("건") || pageText.Contains("검색결과");
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(2000);
throw new Exception($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,209 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Web;
using UniMarc.SearchModel;
using System.Text;
namespace BokBonCheck
{
public class MokpoLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://mokpolib.or.kr/dls_lt/index.php";
public bool HttpApiMode { get; set; } = true;
public int No { get; set; }
private static readonly HttpClient _httpClient = new HttpClient()
{
DefaultRequestHeaders =
{
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
}
};
public MokpoLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"목포시립({areaName})";
}
public async Task StartDriver(bool showdriver = false)
{
// HTTP 클라이언트 사용으로 별도 드라이버 불필요
await Task.CompletedTask;
}
public void StopDriver()
{
// HTTP 클라이언트 사용으로 별도 정리 불필요
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 검색어 URL 인코딩
var encodedSearchTerm = HttpUtility.UrlEncode(searchTerm, Encoding.UTF8);
// 검색 URL 구성
var searchUrl = $"{SiteUrl}?mod=wdDataSearch&act=searchResultList&manageSearch=&detailSearch=";
// 도서관 코드가 있으면 추가
if (!string.IsNullOrEmpty(AreaCode))
{
searchUrl += $"&manageCode%5B{AreaCode}%5D={AreaCode}";
}
// 검색 방식을 제목으로 설정하고 검색어 추가
searchUrl += $"&searchItem%5B%5D=title&searchWord%5B%5D={encodedSearchTerm}";
Console.WriteLine($"목포시립도서관 검색 URL: {searchUrl}");
// HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl))
{
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
throw new HttpRequestException($"HTTP {(int)response.StatusCode} {response.StatusCode}: {errorContent}");
}
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = $"검색성공({resultCount}권)";
}
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = $"검색 오류: {ex.Message}";
result.BookCount = 0;
Console.WriteLine($"목포시립도서관 검색 오류: {ex.Message}");
}
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
{
errorMessage = string.Empty;
try
{
// HTML에서 "총 <strong class="cyan">N권(개)</strong>" 패턴 찾기
var patterns = new[]
{
@"총\s*<strong[^>]*class=""cyan""[^>]*>(\d+)권\(개\)</strong>",
@"총\s*<strong[^>]*>(\d+)권\(개\)</strong>",
@"총\s*(\d+)권\(개\)",
@"총\s*<strong[^>]*class=""cyan""[^>]*>(\d+)</strong>",
@"총\s*<strong[^>]*>(\d+)</strong>"
};
foreach (var pattern in patterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
// 검색 결과가 없다는 메시지 확인
if (htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다") ||
htmlContent.Contains("자료가 없습니다") ||
htmlContent.Contains("총 0권(개)"))
{
errorMessage = "검색결과없음";
return 0;
}
// 더 넓은 범위로 숫자 찾기 시도
var generalPatterns = new[]
{
@"총\s*.*?(\d+)\s*권",
@"총\s*.*?(\d+)\s*개",
@"총\s*.*?(\d+)",
@"<strong[^>]*class=""cyan""[^>]*>(\d+)</strong>"
};
foreach (var pattern in generalPatterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
return -1;
}
}
public Task WaitForPageChange(OpenQA.Selenium.Support.UI.WebDriverWait wait)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,396 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using UniMarc.;
using OpenQA.Selenium.Chromium;
using UniMarc.SearchModel;
using System.Runtime.CompilerServices;
namespace BokBonCheck
{
public class NamguLibrarySearcher : ILibrarySearcher
{
protected string AreaCode = "";
public string SiteName { get; protected set; } = "남구통합도서관(전체)";
public string SiteUrl => "https://lib.namgu.gwangju.kr/main/bookSearch";
public bool HttpApiMode { get; set; } = false;
public int No { get; set; }
private ChromiumDriver _driver;
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
private const int SW_MINIMIZE = 6;
public NamguLibrarySearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"남구통합도서관({areaName})";
}
public void StopDriver()
{
if (_driver != null)
{
_driver.Quit();
_driver.Dispose();
_driver = null;
}
}
public async Task StartDriver(bool showdriver = false)
{
if (_driver == null)
{
try
{
if (SeleniumHelper.IsReady == false) await SeleniumHelper.Download();
_driver = await SeleniumHelper.CreateDriver(ShowBrowser:showdriver);
Console.WriteLine("NamguLibrarySearcher Driver 초기화 완료");
}
catch (Exception ex)
{
Console.WriteLine($"NamguLibrarySearcher Driver 초기화 실패: {ex.Message}");
throw new InvalidOperationException($"NamguLibrarySearcher Driver 초기화에 실패했습니다: {ex.Message}", ex);
}
}
}
virtual protected bool SelectLibrary(WebDriverWait wait)
{
IWebElement searchBox = null;
try
{
var selector = "#clickAll";
searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector)));
if (searchBox == null) return false;
if (searchBox.Selected == true)
{
SafeClick(searchBox);
}
selector = AreaCode;
searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector)));
if (searchBox == null) return false;
if (searchBox.Selected == false)
{
SafeClick(searchBox);
}
return true;
}
catch
{
return false;
}
}
protected void SafeClick(IWebElement searchBox)
{
// 안정적인 클릭을 위한 여러 방법 시도
try
{
// 1. JavaScript로 클릭 시도
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].click();", searchBox);
}
catch
{
try
{
// 2. 요소가 보이도록 스크롤 후 클릭
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", searchBox);
Thread.Sleep(500);
searchBox.Click();
}
catch
{
try
{
// 3. Actions 클래스 사용
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
var actions = new OpenQA.Selenium.Interactions.Actions(driver);
actions.MoveToElement(searchBox).Click().Perform();
}
catch
{
// 4. 마지막 방법: JavaScript로 직접 체크 해제
var driver = ((IWrapsDriver)searchBox).WrappedDriver;
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].checked = false;", searchBox);
}
}
}
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 드라이버가 없으면 자동으로 시작
if (_driver == null)
{
await StartDriver();
}
_driver.Navigate().GoToUrl(SiteUrl);
// 페이지 로딩 대기
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(15));
// 모든 감지 방법이 끝나면 크롬창 최소화
// whale 브라우저가 최소화되어 우선해제
//IntPtr chromeWindow = FindWindow("Chrome_WidgetWin_1", null);
//if (chromeWindow != IntPtr.Zero)
//{
// ShowWindow(chromeWindow, SW_MINIMIZE);
//}
//대상도서관 선택
if (SelectLibrary(wait) == false)
{
result.ErrorMessage = "도서관선택실패";
result.BookCount = -1;
result.IsSuccess = false;
return result;
}
// 검색창 찾기 (남구통합도서관 사이트의 특정 선택자 사용)
IWebElement searchBox = null;
try
{
// 여러 가능한 선택자 시도
var selectors = new[]
{
"input[name='query']",
"input[id='query']",
"input[type='text']",
};
foreach (var selector in selectors)
{
try
{
searchBox = wait.Until(d => d.FindElement(By.CssSelector(selector)));
break;
}
catch
{
continue;
}
}
if (searchBox == null)
{
throw new Exception("검색창을 찾을 수 없습니다.");
}
}
catch (Exception ex)
{
throw new Exception($"검색창 찾기 실패: {ex.Message}");
}
// 검색어 입력
searchBox.Clear();
searchBox.SendKeys(searchTerm);
// 검색 버튼 클릭
IWebElement searchButton = null;
try
{
var buttonSelectors = new[]
{
"button[type='submit']",
"input[type='submit']",
".search-btn",
".btn-search",
"button:contains('검색')",
"input[value*='검색']",
"button[class*='search']",
"input[class*='search']"
};
foreach (var selector in buttonSelectors)
{
try
{
searchButton = _driver.FindElement(By.CssSelector(selector));
break;
}
catch
{
continue;
}
}
if (searchButton == null)
{
// Enter 키로 검색 시도
searchBox.SendKeys(Keys.Enter);
}
else
{
searchButton.Click();
}
}
catch (Exception ex)
{
// Enter 키로 검색 시도
searchBox.SendKeys(Keys.Enter);
}
// 페이지 변경을 감지하는 메서드
await WaitForPageChange(new WebDriverWait(_driver, TimeSpan.FromSeconds(15)));
// 검색 결과 수 추출
var resultCount = ExtractBookCount(_driver, searchTerm, out string ermsg);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = ermsg;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = ermsg;
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(IWebDriver driver, string searchTerm, out string errmessage)
{
errmessage = string.Empty;
try
{
// div.search-result 내부의 span에서 '전체 N' 텍스트 추출
var resultDiv = driver.FindElement(By.CssSelector("div.search-result"));
var bodytext = resultDiv.Text;
if (bodytext.Contains("검색결과가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
}
var searchkey = resultDiv.FindElement(By.XPath("//*[@id=\"sub\"]/section[3]/div/div/div/div/div[2]/div[1]/p/b"));
var searchtitle = searchkey.Text;
if (searchTerm.Contains(searchtitle) == false)
{
errmessage = $"검색어불일치({searchtitle}/{searchTerm})";
return -1;
}
var span = resultDiv.FindElement(By.XPath(".//span[contains(text(),'전체')]"));
string text = span.Text; // 예: "전체 5 "
var match = System.Text.RegularExpressions.Regex.Match(text, @"전체\s*(\d+)");
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int vqty) == false)
{
errmessage = $"수량값오류({match.Groups[1].Value})";
return -1;
}
else
{
searchTerm = string.Empty;
return vqty;
}
}
else
{
errmessage = "수량항목없음";
return -1;
}
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드
public async Task WaitForPageChange(WebDriverWait wait)
{
try
{
await Task.Delay(500);
// 방법 4: 페이지 로딩 상태 확인
wait.Until(d =>
{
var readyState = ((IJavaScriptExecutor)d).ExecuteScript("return document.readyState");
return readyState.Equals("complete");
});
// 방법 5: 특정 텍스트가 페이지에 나타날 때까지 대기
wait.Until(d =>
{
try
{
var byclassname = By.ClassName("search-result");
var elm = d.FindElement(byclassname);
if (elm == null)
{
return false;
}
var pageText = elm.Text;
if (pageText.Contains("검색결과가 없습니다")) return true;
return pageText.Contains("에 대하여") && pageText.Contains("검색되었습니다");
}
catch
{
return false;
}
});
}
catch (Exception ex)
{
// 모든 감지 방법이 실패하면 최소한의 대기 시간 적용
await Task.Delay(2000);
throw new Exception($"페이지 변경 감지 실패: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,800 @@
using AR;
using BokBonCheck;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Chromium;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Interactions;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebDriverManager;
using WebDriverManager.DriverConfigs;
using WebDriverManager.DriverConfigs.Impl;
namespace UniMarc.SearchModel
{
public static class SeleniumHelper
{
public enum eBrowserType
{
edge,
chrome
}
//####### public
public static string DriverPath { get; private set; }
public static eBrowserType Browser = eBrowserType.edge;
/// <summary>
/// test 혹은 create 가 성공하면 이 값이 True 가 됩니다.
/// </summary>
public static bool IsReady { get; private set; } = false;
/// <summary>
/// 사용자데이터 폴더의 기본 경로를 반환합니다.
/// </summary>
public static string UserData_BaseDirectory
{
get
{
return System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"WebDriver", "UserData");
}
}
/// <summary>
/// 드라이버파일을 다운로드 하고 완료된 경우 드라이버 실행파일 명을 반환합니다.
/// </summary>
/// <param name="versiontype">MatchingBrowser,Latest</param>
/// <returns></returns>
public static async Task<string> Download(string versiontype = "MatchingBrowser", DownloadProgressForm progressForm = null)
{
if (progressForm != null)
progressForm.UpdateProgress(30, $"{Browser} 드라이버 다운로드 중...");
var dnpath = new System.IO.DirectoryInfo("WebDriver\\Download");
if (dnpath.Exists == false) dnpath.Create();
var drv = new DriverManager(dnpath.FullName);
IDriverConfig config = null;
if (Browser == eBrowserType.edge) config = new EdgeConfig();
else if (Browser == eBrowserType.chrome) config = new ChromeConfig();
DriverPath = drv.SetUpDriver(config, versiontype);
if (DriverPath.isEmpty() == false && System.IO.File.Exists(DriverPath))
{
Console.WriteLine($"드라이버 다운로드 성공: {DriverPath}");
}
else
{
Console.WriteLine($"드라이버파일이 존재하지 않습니다 파일명:{DriverPath}");
}
await Task.Delay(1);
return DriverPath;
}
/// <summary>
/// 랜덤으로 사용자 폴더를 생성하여 폴더명을 반환합니다
/// 현재 실행되는 폴더에 WebDriver 폴더를 생성하고, 브라우저 종류와 타임스탬프, 랜덤 ID를 포함한 폴더명을 만듭니다.
/// </summary>
/// <returns></returns>
static string MakeUserDataPath()
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
string randomId = Guid.NewGuid().ToString("N").Substring(0, 8);
var userDataDir = System.IO.Path.Combine(UserData_BaseDirectory, $"{Browser}_{timestamp}_{randomId}");
var userpath = new System.IO.DirectoryInfo(userDataDir);
if (userpath.Exists == false) userpath.Create();
return userpath.FullName;
}
/// <summary>
/// 설치된 브라우저를 확인하고 우선순위에 따라 드라이버 생성 테스트를 실행합니다
/// 우선순위: Edge > Chrome
/// </summary>
/// <returns>테스트 성공 여부</returns>
public static async Task<bool> TestDriver(DownloadProgressForm progressForm = null)
{
try
{
// 설치된 브라우저 확인
bool edgeInstalled = IsEdgeInstalled();
bool chromeInstalled = IsChromeInstalled();
if (progressForm != null)
progressForm.UpdateProgress(10, "설치된 브라우저 확인 중...");
// 둘 다 설치되어 있지 않으면 오류
if (!edgeInstalled && !chromeInstalled)
{
IsReady = false;
throw new Exception("Chrome 또는 Edge 브라우저가 설치되어 있지 않습니다. 브라우저를 설치한 후 다시 시도하세요.");
}
// Edge 우선 테스트 (설치되어 있는 경우)
if (edgeInstalled)
{
Console.WriteLine("Edge 브라우저로 드라이버 테스트를 시작합니다.");
if (progressForm != null)
progressForm.UpdateProgress(20, "Edge 브라우저 테스트 중...");
var originalBrowser = Browser;
Browser = eBrowserType.edge;
try
{
await Download(progressForm: progressForm);
var options = CreateBaseBrowserOption(true);
options.AddArgument("--log-level=3");
options.AddArgument("--silent");
options.AddArgument("--disable-blink-features=AutomationControlled");
options.AddArgument("--enable-aggressive-domstorage-flushing");
var driver = await CreateDriver(options, progressForm);
if (driver != null)
{
driver.Quit();
driver.Dispose();
IsReady = true;
Console.WriteLine("Edge 브라우저 드라이버 테스트 성공");
return true;
}
}
catch (Exception ex)
{
Console.WriteLine($"Edge 브라우저 테스트 실패: {ex.Message}");
Browser = originalBrowser;
}
}
// Chrome 테스트 (Edge 실패 시 또는 Edge가 설치되지 않은 경우)
if (chromeInstalled)
{
Console.WriteLine("Chrome 브라우저로 드라이버 테스트를 시작합니다.");
if (progressForm != null)
progressForm.UpdateProgress(60, "Chrome 브라우저 테스트 중...");
var originalBrowser = Browser;
Browser = eBrowserType.chrome;
try
{
await Download(progressForm: progressForm);
var options = CreateBaseBrowserOption(true);
options.AddArgument("--log-level=3");
options.AddArgument("--silent");
options.AddArgument("--disable-blink-features=AutomationControlled");
options.AddArgument("--enable-aggressive-domstorage-flushing");
var driver = await CreateDriver(options, progressForm);
if (driver != null)
{
driver.Quit();
driver.Dispose();
IsReady = true;
Console.WriteLine("Chrome 브라우저 드라이버 테스트 성공");
return true;
}
}
catch (Exception ex)
{
Console.WriteLine($"Chrome 브라우저 테스트 실패: {ex.Message}");
Browser = originalBrowser;
}
}
// 모든 테스트 실패
IsReady = false;
throw new Exception("설치된 모든 브라우저에서 드라이버 테스트가 실패했습니다.");
}
catch (Exception ex)
{
IsReady = false;
Console.WriteLine($"드라이버 생성 테스트 중 오류: {ex.Message}");
if (progressForm != null)
progressForm.UpdateProgress(100, $"테스트 실패: {ex.Message}");
return false;
}
}
public static async Task<ChromiumDriver> CreateDriver(ChromiumOptions options = null, DownloadProgressForm progressForm = null, bool ShowBrowser = false)
{
if (progressForm != null)
progressForm.UpdateProgress(100, $"{Browser} 드라이버 생성 중...");
try
{
ChromiumDriver driver = null;
if (options == null)
{
Console.WriteLine("브라우저 옵션을 기본으로 설정합니다");
options = CreateBaseBrowserOption(!ShowBrowser);
}
if (Browser == eBrowserType.edge)
{
// Edge 드라이버 서비스 생성 (콘솔창 숨김)
var edgeService = string.IsNullOrEmpty(DriverPath) ?
EdgeDriverService.CreateDefaultService() :
EdgeDriverService.CreateDefaultService(System.IO.Path.GetDirectoryName(DriverPath));
edgeService.HideCommandPromptWindow = true;
edgeService.SuppressInitialDiagnosticInformation = true;
driver = new EdgeDriver(edgeService, (EdgeOptions)options);
}
else if (Browser == eBrowserType.chrome)
{
// Chrome 드라이버 서비스 생성 (콘솔창 숨김)
var chromeService = string.IsNullOrEmpty(DriverPath) ?
ChromeDriverService.CreateDefaultService() :
ChromeDriverService.CreateDefaultService(System.IO.Path.GetDirectoryName(DriverPath));
chromeService.HideCommandPromptWindow = true;
chromeService.SuppressInitialDiagnosticInformation = true;
driver = new ChromeDriver(chromeService, (ChromeOptions)options);
}
await Task.Delay(1);
if (driver != null)
{
// 웹드라이버 감지 방지 (안전한 방법)
try
{
((IJavaScriptExecutor)driver).ExecuteScript(@"
if (typeof navigator.webdriver !== 'undefined') {
try {
delete navigator.webdriver;
} catch(e) {}
try {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
configurable: true
});
} catch(e) {}
}
");
Console.WriteLine($"웹드라이버 감지 방지 스크립트 실행 완료");
}
catch (Exception ex)
{
Console.WriteLine($"웹드라이버 감지 방지 스크립트 실행 중 오류 (무시됨): {ex.Message}");
}
// 브라우저 배율을 80%로 설정 (브라우저가 표시되는 경우에만)
try
{
if (driver.Manage().Window.Size.Width > 0) // 브라우저가 표시되는 경우
{
((IJavaScriptExecutor)driver).ExecuteScript("document.body.style.zoom = '0.8';");
Console.WriteLine("기본 브라우저 배율을 80%로 설정했습니다.");
}
}
catch (Exception zoomEx)
{
Console.WriteLine($"기본 브라우저 배율 설정 실패: {zoomEx.Message}");
}
await Task.Delay(1);
}
IsReady = driver != null;
return driver;
}
catch (Exception ex)
{
IsReady = false;
Console.WriteLine($"드라이버 생성 중 오류: {ex.Message}");
return null;
}
}
static ChromiumOptions CreateBaseBrowserOption(bool hideBrowser = true, int width = 800, int height = 600)
{
ChromiumOptions options = null;
if (Browser == eBrowserType.edge)
{
options = new EdgeOptions();
}
else if (Browser == eBrowserType.chrome)
{
options = new ChromeOptions();
}
//options.AddArgument($"--user-data-dir={userDataDir}");
options.AddArgument("--no-first-run");
options.AddArgument("--no-default-browser-check");
options.AddArgument("--disable-default-apps");
options.AddArgument("--disable-popup-blocking");
options.AddArgument("--disable-translate");
options.AddArgument("--disable-background-timer-throttling");
options.AddArgument("--disable-renderer-backgrounding");
options.AddArgument("--disable-backgrounding-occluded-windows");
options.AddArgument("--disable-client-side-phishing-detection");
options.AddArgument("--disable-sync");
options.AddArgument("--disable-extensions");
options.AddArgument("--disable-component-extensions-with-background-pages");
options.AddArgument("--disable-background-networking");
options.AddArgument("--disable-background-mode");
options.AddArgument("--no-sandbox");
options.AddArgument("--disable-dev-shm-usage");
options.AddArgument("--disable-blink-features=AutomationControlled");
options.AddArgument("--remote-debugging-port=0"); // 랜덤 포트 사용
if (hideBrowser) options.AddArgument("--headless");
options.AddArgument($"--window-size={width},{height}");
return options;
}
public static async Task KillExistingDriversAsync()
{
string[] processNames = { };
if (Browser == eBrowserType.chrome)
processNames = new string[] { "chromedriver", "chrome" };
else if (Browser == eBrowserType.edge)
processNames = new string[] { "msedgedriver", "msedge", "MicrosoftEdge" };
try
{
foreach (string processName in processNames)
{
Process[] processes = Process.GetProcessesByName(processName);
if (processes.Length > 0)
{
Console.WriteLine($"기존 {processName} 프로세스 {processes.Length}개를 종료하는 중...");
var killTasks = new List<Task>();
foreach (Process process in processes)
{
killTasks.Add(Task.Run(() =>
{
try
{
process.Kill();
process.WaitForExit(3000);
}
catch (Exception ex)
{
Console.WriteLine($"{processName} 종료 중 오류: {ex.Message}");
}
}));
}
await Task.WhenAll(killTasks);
}
}
// 임시 WebDriver 사용자 데이터 폴더들 정리
await Task.Run(() => ClearDriverCache());
// 프로세스가 완전히 종료될 때까지 대기
await Task.Delay(2000);
}
catch (Exception ex)
{
Console.WriteLine($"기존 프로세스 종료 중 오류: {ex.Message}");
}
}
/// <summary>
/// 사용자데이터 폴더를 정리합니다.
/// </summary>
static void ClearDriverCache()
{
try
{
Console.WriteLine("Chrome 드라이버 캐시 정리 시작...");
if (System.IO.Directory.Exists(UserData_BaseDirectory))
{
string[] directories = System.IO.Directory.GetDirectories(UserData_BaseDirectory);
if (directories.Length > 0)
{
Console.WriteLine($"기존 임시 사용자 데이터 폴더 {directories.Length}개를 정리하는 중...");
foreach (string dir in directories)
{
try
{
System.IO.Directory.Delete(dir, true);
}
catch (Exception ex)
{
Console.WriteLine($"폴더 삭제 중 오류: {ex.Message}");
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"임시 폴더 정리 중 오류: {ex.Message}");
}
}
public static void KillExistingDrivers()
{
KillExistingDriversAsync().GetAwaiter().GetResult();
}
public static void Dispose()
{
try
{
KillExistingDrivers();
ClearDriverCache();
}
catch (Exception ex)
{
Console.WriteLine($"Exception-Dispose={ex.Message}");
}
finally
{
IsReady = false;
}
}
public static async Task DisposeAsync()
{
try
{
await KillExistingDriversAsync();
ClearDriverCache();
}
catch (Exception ex)
{
Console.WriteLine($"Exception-DisposeAsync={ex.Message}");
}
finally
{
IsReady = false;
}
}
#region "브라우저별 버젼 확인"
/// <summary>
/// 현재 설치된 브라우저 버전을 확인합니다.
/// </summary>
/// <returns>브라우저 버전 문자열</returns>
public static string GetBrowserVersion()
{
try
{
if (Browser == eBrowserType.chrome)
{
return GetChromeVersion();
}
else if (Browser == eBrowserType.edge)
{
return GetEdgeVersion();
}
return "Unknown Browser";
}
catch (Exception ex)
{
return $"Version check failed: {ex.Message}";
}
}
/// <summary>
/// Chrome 브라우저 버전을 확인합니다.
/// </summary>
/// <returns>Chrome 버전</returns>
static string GetChromeVersion()
{
try
{
// 레지스트리에서 Chrome 버전 확인
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Google\Chrome\BLBeacon"))
{
if (key != null)
{
var version = key.GetValue("version")?.ToString();
if (!string.IsNullOrEmpty(version))
return $"Chrome {version}";
}
}
// HKLM에서 시도
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Google\Chrome\BLBeacon"))
{
if (key != null)
{
var version = key.GetValue("version")?.ToString();
if (!string.IsNullOrEmpty(version))
return $"Chrome {version}";
}
}
// WOW6432Node에서 시도 (32비트 앱이 64비트 시스템에서 실행될 때)
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Google\Chrome\BLBeacon"))
{
if (key != null)
{
var version = key.GetValue("version")?.ToString();
if (!string.IsNullOrEmpty(version))
return $"Chrome {version}";
}
}
return "Chrome version not found";
}
catch (Exception ex)
{
return $"Chrome version check failed: {ex.Message}";
}
}
/// <summary>
/// Edge 브라우저 버전을 확인합니다.
/// </summary>
/// <returns>Edge 버전</returns>
static string GetEdgeVersion()
{
try
{
// 레지스트리에서 Edge 버전 확인
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Edge\BLBeacon"))
{
if (key != null)
{
var version = key.GetValue("version")?.ToString();
if (!string.IsNullOrEmpty(version))
return $"Edge {version}";
}
}
// HKLM에서 시도
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Edge\BLBeacon"))
{
if (key != null)
{
var version = key.GetValue("version")?.ToString();
if (!string.IsNullOrEmpty(version))
return $"Edge {version}";
}
}
// WOW6432Node에서 시도
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Microsoft\Edge\BLBeacon"))
{
if (key != null)
{
var version = key.GetValue("version")?.ToString();
if (!string.IsNullOrEmpty(version))
return $"Edge {version}";
}
}
return "Edge version not found";
}
catch (Exception ex)
{
return $"Edge version check failed: {ex.Message}";
}
}
#endregion
#region "브라우저 설치 확인"
/// <summary>
/// Chrome 브라우저가 설치되어 있는지 확인합니다.
/// 관리자 권한 없이도 작동합니다.
/// </summary>
/// <returns>Chrome 설치 여부</returns>
public static bool IsChromeInstalled()
{
try
{
// 1. 레지스트리 확인 (CurrentUser)
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Google\Chrome\BLBeacon"))
{
if (key != null && key.GetValue("version") != null)
return true;
}
// 2. 레지스트리 확인 (LocalMachine) - 읽기 권한은 일반 사용자도 가능
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Google\Chrome\BLBeacon"))
{
if (key != null && key.GetValue("version") != null)
return true;
}
}
catch
{
// 접근 권한이 없어도 계속 진행
}
// 3. WOW6432Node 확인
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Google\Chrome\BLBeacon"))
{
if (key != null && key.GetValue("version") != null)
return true;
}
}
catch
{
// 접근 권한이 없어도 계속 진행
}
// 4. 기본 설치 경로 확인
string[] chromePaths = {
@"C:\Program Files\Google\Chrome\Application\chrome.exe",
@"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\Application\chrome.exe")
};
foreach (string path in chromePaths)
{
if (System.IO.File.Exists(path))
return true;
}
return false;
}
catch (Exception ex)
{
Console.WriteLine($"Chrome 설치 확인 중 오류: {ex.Message}");
return false;
}
}
/// <summary>
/// Edge 브라우저가 설치되어 있는지 확인합니다.
/// 관리자 권한 없이도 작동합니다.
/// </summary>
/// <returns>Edge 설치 여부</returns>
public static bool IsEdgeInstalled()
{
try
{
// 1. 레지스트리 확인 (CurrentUser)
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Edge\BLBeacon"))
{
if (key != null && key.GetValue("version") != null)
return true;
}
// 2. 레지스트리 확인 (LocalMachine) - 읽기 권한은 일반 사용자도 가능
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Edge\BLBeacon"))
{
if (key != null && key.GetValue("version") != null)
return true;
}
}
catch
{
// 접근 권한이 없어도 계속 진행
}
// 3. WOW6432Node 확인
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Microsoft\Edge\BLBeacon"))
{
if (key != null && key.GetValue("version") != null)
return true;
}
}
catch
{
// 접근 권한이 없어도 계속 진행
}
// 4. 기본 설치 경로 확인
string[] edgePaths = {
@"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
@"C:\Program Files\Microsoft\Edge\Application\msedge.exe"
};
foreach (string path in edgePaths)
{
if (System.IO.File.Exists(path))
return true;
}
// 5. Windows 10/11의 기본 Edge 확인 (시스템 앱)
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Classes\MSEdgeHTM"))
{
if (key != null)
return true;
}
}
catch
{
// 접근 권한이 없어도 계속 진행
}
return false;
}
catch (Exception ex)
{
Console.WriteLine($"Edge 설치 확인 중 오류: {ex.Message}");
return false;
}
}
/// <summary>
/// 설치된 브라우저 목록을 반환합니다.
/// </summary>
/// <returns>설치된 브라우저 목록</returns>
public static List<eBrowserType> GetInstalledBrowsers()
{
var installedBrowsers = new List<eBrowserType>();
if (IsChromeInstalled())
installedBrowsers.Add(eBrowserType.chrome);
if (IsEdgeInstalled())
installedBrowsers.Add(eBrowserType.edge);
return installedBrowsers;
}
/// <summary>
/// 현재 설정된 브라우저가 설치되어 있는지 확인합니다.
/// </summary>
/// <returns>현재 브라우저 설치 여부</returns>
public static bool IsBrowserInstalled()
{
switch (Browser)
{
case eBrowserType.chrome:
return IsChromeInstalled();
case eBrowserType.edge:
return IsEdgeInstalled();
default:
return false;
}
}
/// <summary>
/// 브라우저 설치 상태 정보를 문자열로 반환합니다.
/// </summary>
/// <returns>브라우저 설치 상태 정보</returns>
public static string GetBrowserInstallationStatus()
{
var status = new StringBuilder();
status.AppendLine("=== 브라우저 설치 상태 ===");
bool chromeInstalled = IsChromeInstalled();
bool edgeInstalled = IsEdgeInstalled();
status.AppendLine($"Chrome: {(chromeInstalled ? "" : "")}");
if (chromeInstalled)
status.AppendLine($" 버전: {GetChromeVersion()}");
status.AppendLine($"Edge: {(edgeInstalled ? "" : "")}");
if (edgeInstalled)
status.AppendLine($" 버전: {GetEdgeVersion()}");
status.AppendLine($"현재 설정: {Browser} ({(IsBrowserInstalled() ? " " : " ")})");
return status.ToString();
}
#endregion
}
}

View File

@@ -0,0 +1,229 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Web;
using UniMarc.SearchModel;
using System.Text;
namespace BokBonCheck
{
public class SuncheonLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://library.suncheon.go.kr/lib/book/search/searchIndex.do";
public bool HttpApiMode { get; set; } = true;
public int No { get; set; }
private static readonly HttpClient _httpClient = new HttpClient()
{
DefaultRequestHeaders =
{
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
}
};
public SuncheonLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"순천시립({areaName})";
}
public async Task StartDriver(bool showdriver = false)
{
// HTTP 클라이언트 사용으로 별도 드라이버 불필요
await Task.CompletedTask;
}
public void StopDriver()
{
// HTTP 클라이언트 사용으로 별도 정리 불필요
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 검색어 URL 인코딩
var encodedSearchTerm = HttpUtility.UrlEncode(searchTerm, Encoding.UTF8);
// 검색 URL 구성
var searchUrl = $"{SiteUrl}?menuCd=L001001001&alpha=&vcindex=&currentPageNo=1&nPageSize=10&searchType=title&search={encodedSearchTerm}&mediaCode=";
// 도서관 코드가 있으면 추가
if (!string.IsNullOrEmpty(AreaCode))
{
if (AreaCode == "mini")
{
// 작은도서관의 경우 모든 코드를 포함
searchUrl += "&manageCd=AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR,AS,AT,AU,AV,AW,AX,AY,AZ,BA,BB,BC,BD,BF,BG,BH,BI,BJ,BM,BQ,BS,BW,CA,CB,CC,CD,CE,CF,CG,CH,CI,CJ,CK,CL,CM,CN,CO,CP,CQ,DA,DB,DC,DD,DE,DF,DG,DH,DI,DJ,DK,DL,DS,GD";
}
else
{
searchUrl += $"&manageCd={AreaCode}";
}
}
Console.WriteLine($"순천시립도서관 검색 URL: {searchUrl}");
// HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl))
{
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
throw new HttpRequestException($"HTTP {(int)response.StatusCode} {response.StatusCode}: {errorContent}");
}
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = $"검색성공({resultCount}권)";
}
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = $"검색 오류: {ex.Message}";
result.BookCount = 0;
Console.WriteLine($"순천시립도서관 검색 오류: {ex.Message}");
}
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
{
errorMessage = string.Empty;
try
{
// HTML에서 "총 <strong class="cred">N</strong>건" 패턴 찾기
var patterns = new[]
{
@"총\s*<strong[^>]*class=""cred""[^>]*>(\d+)</strong>\s*건",
@"총\s*<strong[^>]*>(\d+)</strong>\s*건",
@"총\s*(\d+)\s*건",
@"<strong[^>]*class=""cred""[^>]*>(\d+)</strong>"
};
foreach (var pattern in patterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
// 검색 결과가 없다는 메시지 확인
if (htmlContent.Contains("검색결과가 없습니다") ||
htmlContent.Contains("검색된 자료가 없습니다") ||
htmlContent.Contains("자료가 없습니다") ||
htmlContent.Contains("총 0건"))
{
errorMessage = "검색결과없음";
return 0;
}
// resultCon 영역에서 더 자세히 검색
var resultConPattern = @"<div[^>]*class=""resultCon[^""]*""[^>]*>.*?총\s*<strong[^>]*>(\d+)</strong>\s*건.*?</div>";
var resultConMatch = Regex.Match(htmlContent, resultConPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
if (resultConMatch.Success)
{
if (int.TryParse(resultConMatch.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
return count;
}
}
// 더 넓은 범위로 숫자 찾기 시도
var generalPatterns = new[]
{
@"검색한\s*결과\s*총\s*<strong[^>]*>(\d+)</strong>",
@"검색결과를\s*찾았습니다[^>]*>(\d+)</strong>",
@"<strong[^>]*class=""cred""[^>]*>(\d+)</strong>"
};
foreach (var pattern in generalPatterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errorMessage = "검색결과없음";
return 0;
}
errorMessage = $"검색성공({count}권)";
return count;
}
}
}
errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1;
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
return -1;
}
}
public Task WaitForPageChange(OpenQA.Selenium.Support.UI.WebDriverWait wait)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,182 @@
using System;
using System.Threading.Tasks;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System.Text.RegularExpressions;
using WebDriverManager;
using WebDriverManager.DriverConfigs.Impl;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using UniMarc.;
using OpenQA.Selenium.Chromium;
using UniMarc.SearchModel;
using System.Runtime.CompilerServices;
using AR;
using System.Net.Http;
namespace BokBonCheck
{
public class WandoLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://wandolib.kr/dls_le/index.php";
public bool HttpApiMode { get; set; } = true;
public int No { get; set; }
private ChromiumDriver _driver;
private static readonly HttpClient _httpClient = new HttpClient()
{
DefaultRequestHeaders =
{
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
}
};
public WandoLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"완도군립({areaName})";
}
public void StopDriver()
{
// HTTP 클라이언트 사용으로 별도 정리 불필요
}
public async Task StartDriver(bool showdriver = false)
{
// HTTP 클라이언트 사용으로 별도 드라이버 불필요
await Task.CompletedTask;
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 검색어 URL 인코딩
var encodedSearchTerm = System.Web.HttpUtility.UrlEncode(searchTerm, System.Text.Encoding.UTF8);
// 검색 URL 구성
var searchUrl = $"{SiteUrl}?mod=wdDataSearch&act=searchIList&item=title&word={encodedSearchTerm}";
// 도서관 코드가 있으면 추가
if (!string.IsNullOrEmpty(AreaCode))
{
searchUrl += $"&manageCode%5B{AreaCode}%5D={AreaCode}";
}
Console.WriteLine($"완도군립도서관 검색 URL: {searchUrl}");
// HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl))
{
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = $"검색성공({resultCount}권)";
}
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = ex.Message;
result.BookCount = 0;
}
return result;
}
private int ExtractBookCount(string htmlContent, out string errmessage)
{
errmessage = string.Empty;
try
{
// 검색 결과가 없다는 메시지 확인
if (htmlContent.Contains("0권(개)") || htmlContent.Contains("검색결과가 없습니다"))
{
errmessage = "검색결과없음";
return 0;
}
// HTML에서 다양한 패턴 찾기
var htmlPatterns = new[]
{
@"총\s*<strong[^>]*class=""cyan""[^>]*>\s*(\d+)\s*권\(개\)",
@"총\s*(\d+)\s*권\(개\)",
@"<strong[^>]*class=""cyan""[^>]*>\s*(\d+)\s*권",
@"(\d+)\s*권\(개\)"
};
foreach (var pattern in htmlPatterns)
{
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
if (count == 0)
{
errmessage = "검색결과없음";
return 0;
}
errmessage = $"검색성공({count}권)";
return count;
}
}
}
errmessage = "결과수량을찾을수없음";
return -1;
}
catch (Exception ex)
{
errmessage = ex.Message;
return -1;
}
}
// 페이지 변경을 감지하는 메서드 (HTTP 방식에서는 불필요)
public async Task WaitForPageChange(WebDriverWait wait)
{
// HTTP 방식에서는 즉시 응답이 오므로 대기 불필요
await Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,177 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Web;
using UniMarc.SearchModel;
using System.Text;
using OpenQA.Selenium.Support.UI;
namespace BokBonCheck
{
public class YeosuLibSearcher : ILibrarySearcher
{
protected string AreaCode { get; set; } = string.Empty;
public string SiteName { get; protected set; }
public string SiteUrl => "https://yslib.yeosu.go.kr/dls_kapi/index.php";
public int No { get; set; }
public bool HttpApiMode { get; set; } = true;
private static readonly HttpClient _httpClient = new HttpClient()
{
DefaultRequestHeaders =
{
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
}
};
public YeosuLibSearcher(int no, string areaCode, string areaName)
{
this.No = no;
this.AreaCode = areaCode;
this.SiteName = $"여수시립({areaName})";
}
public async Task StartDriver(bool showdriver = false)
{
// HTTP 클라이언트 사용으로 별도 드라이버 불필요
await Task.CompletedTask;
}
public void StopDriver()
{
// HTTP 클라이언트 사용으로 별도 정리 불필요
}
public async Task<BookSearchResult> SearchAsync(string searchTerm)
{
var result = new BookSearchResult
{
SiteName = SiteName,
SearchTerm = searchTerm,
SearchTime = DateTime.Now
};
try
{
// 검색어 URL 인코딩
var encodedSearchTerm = HttpUtility.UrlEncode(searchTerm, Encoding.UTF8);
// 검색 URL 구성
var searchUrl = $"{SiteUrl}?act=searchResultList&mod=wdDataSearch";
// 지역코드가 있으면 추가 (전체인 경우 facetManageCode 생략)
if (!string.IsNullOrEmpty(AreaCode))
{
searchUrl += $"&facetManageCode={AreaCode}";
}
searchUrl += $"&searchWord={encodedSearchTerm}&author=&keyword=&publisher=&pubYearS=&pubYearE=&dataSort=rk+desc";
Console.WriteLine($"여수시립도서관 검색 URL: {searchUrl}");
// HTTP GET 요청 실행 (추가 헤더 포함)
using (var request = new HttpRequestMessage(HttpMethod.Get, searchUrl))
{
// 브라우저와 유사한 헤더 추가
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
request.Headers.Add("Accept-Language", "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
request.Headers.Add("Connection", "keep-alive");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var htmlContent = await response.Content.ReadAsStringAsync();
// 검색 결과 수 추출
var resultCount = ExtractBookCount(htmlContent, out string errorMessage);
if (resultCount == -1)
{
result.BookCount = 0;
result.IsSuccess = false;
result.ErrorMessage = errorMessage;
}
else
{
result.BookCount = resultCount;
result.IsSuccess = true;
result.ErrorMessage = $"검색성공({resultCount}권)";
}
}
}
catch (Exception ex)
{
result.IsSuccess = false;
result.ErrorMessage = $"검색 오류: {ex.Message}";
result.BookCount = 0;
Console.WriteLine($"여수시립도서관 검색 오류: {ex.Message}");
}
return result;
}
private int ExtractBookCount(string htmlContent, out string errorMessage)
{
errorMessage = string.Empty;
try
{
// HTML에서 "총 N권(개)" 패턴 찾기
var pattern = @"총\s*<strong[^>]*class=""cyan""[^>]*>(\d+)권\(개\)</strong>";
var match = Regex.Match(htmlContent, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int count))
{
errorMessage = $"검색성공({count}권)";
return count;
}
else
{
errorMessage = $"수량값오류({match.Groups[1].Value})";
return -1;
}
}
else
{
// 패턴을 찾지 못한 경우 다른 패턴으로 시도
var alternatePattern = @"총\s*<strong[^>]*>(\d+)권\(개\)</strong>";
var alternateMatch = Regex.Match(htmlContent, alternatePattern, RegexOptions.IgnoreCase);
if (alternateMatch.Success)
{
if (int.TryParse(alternateMatch.Groups[1].Value, out int count))
{
errorMessage = $"검색성공({count}권)";
return count;
}
}
errorMessage = "검색결과 패턴을 찾을 수 없음";
return -1;
}
}
catch (Exception ex)
{
errorMessage = $"결과 분석 오류: {ex.Message}";
return -1;
}
}
public Task WaitForPageChange(WebDriverWait wait)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,6 +1,22 @@
namespace UniMarc.Properties {
using System.Security.Permissions;
namespace UniMarc.Properties {
public class UserSetting : AR.Setting
{
public string LastSearchTarget { get; set; }
public string LastSearchTargetDLS { get; set; }
public override void AfterLoad()
{
}
public override void AfterSave()
{
}
}
// 이 클래스를 사용하여 설정 클래스에 대한 특정 이벤트를 처리할 수 있습니다.
// SettingChanging 이벤트는 설정 값이 변경되기 전에 발생합니다.
// PropertyChanged 이벤트는 설정 값이 변경된 후에 발생합니다.

View File

@@ -2961,8 +2961,11 @@ namespace WindowsFormsApp1
}
public string VersionInfo()
{
string version = "";
StreamReader sr = new StreamReader(Application.StartupPath + "\\update.inf");
string version = "0";
var fn = Application.StartupPath + "\\update.inf";
if(System.IO.File.Exists(fn))
{
StreamReader sr = new StreamReader(fn);
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
@@ -2972,6 +2975,8 @@ namespace WindowsFormsApp1
break;
}
}
}
return version;
}
}

View File

@@ -45,6 +45,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<NoWarn>IDE0090,IDE1006</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -74,7 +75,13 @@
<PropertyGroup>
<ManifestKeyFile>UniMarc_TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>UniMarc.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="AngleSharp, Version=1.3.0.0, Culture=neutral, PublicKeyToken=e83494dcdc6d31ea, processorArchitecture=MSIL">
<HintPath>..\packages\AngleSharp.1.3.0\lib\net472\AngleSharp.dll</HintPath>
</Reference>
<Reference Include="arCommUtil">
<HintPath>..\dll\arCommUtil.dll</HintPath>
</Reference>
@@ -84,22 +91,103 @@
<Reference Include="ArLog.Net4">
<HintPath>..\dll\ArLog.Net4.dll</HintPath>
</Reference>
<Reference Include="BouncyCastle.Cryptography, Version=2.0.0.0, Culture=neutral, PublicKeyToken=072edcf4a5328938, processorArchitecture=MSIL">
<HintPath>..\packages\BouncyCastle.Cryptography.2.5.1\lib\net461\BouncyCastle.Cryptography.dll</HintPath>
</Reference>
<Reference Include="CarlosAg.ExcelXmlWriter">
<HintPath>..\dll\CarlosAg.ExcelXmlWriter.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="MySql.Data, Version=8.0.21.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL" />
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Reference Include="Google.Protobuf, Version=3.30.0.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604, processorArchitecture=MSIL">
<HintPath>..\packages\Google.Protobuf.3.30.0\lib\net45\Google.Protobuf.dll</HintPath>
</Reference>
<Reference Include="Renci.SshNet, Version=2020.0.1.0, Culture=neutral, PublicKeyToken=1cee9f8bde3db106, processorArchitecture=MSIL">
<HintPath>..\packages\SSH.NET.2020.0.1\lib\net40\Renci.SshNet.dll</HintPath>
<Reference Include="ICSharpCode.SharpZipLib, Version=1.4.2.13, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.1.4.2\lib\netstandard2.0\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="K4os.Compression.LZ4, Version=1.3.8.0, Culture=neutral, PublicKeyToken=2186fa9121ef231d, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Compression.LZ4.1.3.8\lib\net462\K4os.Compression.LZ4.dll</HintPath>
</Reference>
<Reference Include="K4os.Compression.LZ4.Streams, Version=1.3.8.0, Culture=neutral, PublicKeyToken=2186fa9121ef231d, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Compression.LZ4.Streams.1.3.8\lib\net462\K4os.Compression.LZ4.Streams.dll</HintPath>
</Reference>
<Reference Include="K4os.Hash.xxHash, Version=1.0.8.0, Culture=neutral, PublicKeyToken=32cd54395057cec3, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Hash.xxHash.1.0.8\lib\net462\K4os.Hash.xxHash.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.2, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.2\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.3, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.8.0.3\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="Microsoft.Win32.Registry, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Win32.Registry.5.0.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
</Reference>
<Reference Include="MySql.Data, Version=9.4.0.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
<HintPath>..\packages\MySql.Data.9.4.0\lib\net462\MySql.Data.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Renci.SshNet, Version=2025.0.0.1, Culture=neutral, PublicKeyToken=1cee9f8bde3db106, processorArchitecture=MSIL">
<HintPath>..\packages\SSH.NET.2025.0.0\lib\net462\Renci.SshNet.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.ConfigurationManager, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Configuration.ConfigurationManager.8.0.0\lib\net462\System.Configuration.ConfigurationManager.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.1\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.DirectoryServices" />
<Reference Include="System.Formats.Asn1, Version=8.0.0.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Formats.Asn1.8.0.2\lib\net462\System.Formats.Asn1.dll</HintPath>
</Reference>
<Reference Include="System.IO.Pipelines, Version=5.0.0.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Pipelines.5.0.2\lib\net461\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Management" />
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Security.AccessControl, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.AccessControl.5.0.0\lib\net461\System.Security.AccessControl.dll</HintPath>
</Reference>
<Reference Include="System.Security.Principal.Windows, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel" />
<Reference Include="System.Text.Encoding.CodePages, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encoding.CodePages.8.0.0\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=8.0.0.5, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Json.8.0.5\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Transactions" />
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml.Linq" />
@@ -111,6 +199,18 @@
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="WebDriver, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Selenium.WebDriver.4.34.0\lib\netstandard2.0\WebDriver.dll</HintPath>
</Reference>
<Reference Include="WebDriver.Support, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Selenium.Support.4.34.0\lib\netstandard2.0\WebDriver.Support.dll</HintPath>
</Reference>
<Reference Include="WebDriverManager, Version=2.17.6.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\WebDriverManager.2.17.6\lib\net472\WebDriverManager.dll</HintPath>
</Reference>
<Reference Include="ZstdSharp, Version=0.8.5.0, Culture=neutral, PublicKeyToken=8d151af33a4ad5cf, processorArchitecture=MSIL">
<HintPath>..\packages\ZstdSharp.Port.0.8.5\lib\net462\ZstdSharp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="CExt.cs" />
@@ -124,7 +224,42 @@
<DesignTime>True</DesignTime>
<DependentUpon>Reference.svcmap</DependentUpon>
</Compile>
<Compile Include="CUtill.cs" />
<Compile Include="PUB.cs" />
<Compile Include="SearchModel\AnsanLibSearcher.cs" />
<Compile Include="SearchModel\BookSearchService.cs" />
<Compile Include="SearchModel\BukguLibSearcher.cs" />
<Compile Include="SearchModel\BukguPublicLibSearcher.cs" />
<Compile Include="SearchModel\BukguSlibSearcher.cs" />
<Compile Include="SearchModel\DLSSearcher.cs" />
<Compile Include="SearchModel\DownloadProgressForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="SearchModel\GoheungLibSearcher.cs" />
<Compile Include="SearchModel\GwangjuCityLibSearcher.cs" />
<Compile Include="SearchModel\GwangjuDongguLibSearcher.cs" />
<Compile Include="SearchModel\GwangjuSeoguLibSearcher.cs" />
<Compile Include="SearchModel\GwangjuSeoguSmallLibSearcher.cs" />
<Compile Include="SearchModel\GwangjuSeoguSmartLibSearcher.cs" />
<Compile Include="SearchModel\GwangsanLibSearcher.cs" />
<Compile Include="SearchModel\GyeongnamLibSearcher.cs" />
<Compile Include="SearchModel\IksanLibSearcher.cs" />
<Compile Include="SearchModel\ILibrarySearcher.cs" />
<Compile Include="SearchModel\JeonbukEduLibSearcher.cs" />
<Compile Include="SearchModel\MokpoLibSearcher.cs" />
<Compile Include="SearchModel\SuncheonLibSearcher.cs" />
<Compile Include="SearchModel\WandoLibSearcher.cs" />
<Compile Include="SearchModel\YeosuLibSearcher.cs" />
<Compile Include="SearchModel\JunnamEduJiheaNuriSearcher.cs" />
<Compile Include="SearchModel\KwangjuCityEduLibrarySearcher.cs" />
<Compile Include="SearchModel\JunnamEduSearcher.cs" />
<Compile Include="SearchModel\NamguLibrarySearcher.cs" />
<Compile Include="SearchModel\SeleniumHelper.cs" />
<Compile Include="개발자용\fDevDB.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="개발자용\fDevDB.Designer.cs">
<DependentUpon>fDevDB.cs</DependentUpon>
</Compile>
<Compile Include="마스터\From_User_manage_List.cs">
<SubType>Form</SubType>
</Compile>
@@ -173,6 +308,12 @@
<Compile Include="마크\CD_LP_Sub.Designer.cs">
<DependentUpon>CD_LP_Sub.cs</DependentUpon>
</Compile>
<Compile Include="마크\Check_copyWD.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="마크\Check_copyWD.Designer.cs">
<DependentUpon>Check_copyWD.cs</DependentUpon>
</Compile>
<Compile Include="마크\Check_Copy_Login.cs">
<SubType>Form</SubType>
</Compile>
@@ -843,6 +984,9 @@
<Compile Include="작업일지\Work_Log.Designer.cs">
<DependentUpon>Work_Log.cs</DependentUpon>
</Compile>
<EmbeddedResource Include="개발자용\fDevDB.resx">
<DependentUpon>fDevDB.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="마스터\From_User_manage_List.resx">
<DependentUpon>From_User_manage_List.cs</DependentUpon>
</EmbeddedResource>
@@ -867,6 +1011,9 @@
<EmbeddedResource Include="마크\CD_LP_Sub.resx">
<DependentUpon>CD_LP_Sub.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="마크\Check_copyWD.resx">
<DependentUpon>Check_copyWD.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="마크\Check_Copy_Login.resx">
<DependentUpon>Check_Copy_Login.cs</DependentUpon>
</EmbeddedResource>
@@ -1848,6 +1995,7 @@
<None Include="Resources\0_Empty.png" />
</ItemGroup>
<ItemGroup>
<Content Include="UniMarc.ico" />
<None Include="Connected Services\BaroService_TI\configuration91.svcinfo" />
<None Include="Connected Services\BaroService_TI\configuration.svcinfo" />
<None Include="Connected Services\BaroService_TI\Reference.svcmap">
@@ -1868,4 +2016,11 @@
<Content Include="Resources\3_2_2_편목.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Selenium.WebDriver.4.34.0\build\Selenium.WebDriver.targets" Condition="Exists('..\packages\Selenium.WebDriver.4.34.0\build\Selenium.WebDriver.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>이 프로젝트는 이 컴퓨터에 없는 NuGet 패키지를 참조합니다. 해당 패키지를 다운로드하려면 NuGet 패키지 복원을 사용하십시오. 자세한 내용은 http://go.microsoft.com/fwlink/?LinkID=322105를 참조하십시오. 누락된 파일은 {0}입니다.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Selenium.WebDriver.4.34.0\build\Selenium.WebDriver.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Selenium.WebDriver.4.34.0\build\Selenium.WebDriver.targets'))" />
</Target>
</Project>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PublishUrlHistory>E:\UniMarcApplicationUpdateFiles\|ftp://ftpgloria%401.215.250.130/|ftp://ftpgloria%401.215.250.130:50005/|sftp://ftpgloria%401.215.250.130/|ftp://ftpgloria%401.215.250.130/unimarc/</PublishUrlHistory>
@@ -9,6 +9,7 @@
<ErrorReportUrlHistory />
<FallbackCulture>ko-KR</FallbackCulture>
<VerifyUploadedFiles>false</VerifyUploadedFiles>
<ProjectView>ShowAllFiles</ProjectView>
</PropertyGroup>
<PropertyGroup>
<EnableSecurityDebugging>false</EnableSecurityDebugging>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -1,2 +0,0 @@
ID=sh
PW=123

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
idx,등록일,마감일,작업목록명,연동목록명,분류,전체,편목,미편목,상태,등급,비고,담당자,V,
DataGridViewTextBoxCell { ColumnIndex=0, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=1, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=2, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=3, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=4, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=5, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=6, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=7, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=8, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=9, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=10, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=11, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=12, RowIndex=0 },DataGridViewTextBoxCell { ColumnIndex=13, RowIndex=0 },

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="UniMarc.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/></startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BaroService_FAXSoap" />
<binding name="BaroService_TISoap">
<security mode="Transport" />
</binding>
<binding name="BaroService_TISoap1" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://testws.baroservice.com/FAX.asmx" binding="basicHttpBinding"
bindingConfiguration="BaroService_FAXSoap" contract="BaroService_API.BaroService_FAXSoap"
name="BaroService_FAXSoap" />
<endpoint address="https://testws.baroservice.com/TI.asmx" binding="basicHttpBinding"
bindingConfiguration="BaroService_TISoap" contract="BaroService_TI.BaroService_TISoap"
name="BaroService_TISoap" />
</client>
</system.serviceModel>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Renci.SshNet" publicKeyToken="1cee9f8bde3db106" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2020.0.1.0" newVersion="2020.0.1.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
<userSettings>
<UniMarc.Properties.Settings>
<setting name="compidx" serializeAs="String">
<value />
</setting>
<setting name="User" serializeAs="String">
<value />
</setting>
<setting name="IP" serializeAs="String">
<value>1.11010111.11111010.10000010</value>
</setting>
<setting name="Port" serializeAs="String">
<value>1100101111</value>
</setting>
<setting name="Uid" serializeAs="String">
<value>103.108.111.114.105.97.98.111.111.107</value>
</setting>
<setting name="pwd" serializeAs="String">
<value>97.100.109.105.110.64.33.64.35.36</value>
</setting>
<setting name="dbPort" serializeAs="String">
<value>110011101010</value>
</setting>
<setting name="dbUid" serializeAs="String">
<value>114.111.111.116</value>
</setting>
<setting name="dbPwd" serializeAs="String">
<value>65.100.109.105.110.50.49.50.51.52</value>
</setting>
</UniMarc.Properties.Settings>
</userSettings>
</configuration>

View File

@@ -1,4 +0,0 @@
9788969524102 1 간호사를 간호하는 간호사 경향BP 오성훈 13500 60 8100 [(주)글로리아북]납품 2021-07-14 오후 1:44 머리 10
9791195010073 1 감정조절자(희망) : 나를 힘겹게 하는 나와 작별하기 프로젝트 헥소미아 김인자 16000 60 9600 [(주)글로리아북]납품 2021-07-14 오후 1:44 머리 11
9788932320625 1 개는 우리를 어떻게 사랑하는가 : 개의 특별한 애정에 대한 과학적 탐구 현암사 클라이브 D. L. 윈 ; 전행선 17000 60 10200 [(주)글로리아북]납품 2021-07-14 오후 1:44 머리 12
9791158491307 1 걷다 느끼다 쓰다 : 전문성과 대중성을 겸비한 글쓰기 수업 모아북스 이해사 15000 60 9000 [(주)글로리아북]납품 2021-07-14 오후 1:44 머리 13

View File

@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]

View File

@@ -1 +0,0 @@
a980cc54cfee34721f8734aaae513dd71cd3d934

View File

@@ -1,154 +0,0 @@
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\MySql.Data.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\Newtonsoft.Json.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\Renci.SshNet.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\Google.Protobuf.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\Zstandard.Net.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\K4os.Compression.LZ4.Streams.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\BouncyCastle.Crypto.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\Ubiety.Dns.Core.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\System.Buffers.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\K4os.Compression.LZ4.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\K4os.Hash.xxHash.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\System.Runtime.CompilerServices.Unsafe.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\MySql.Data.xml
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\Newtonsoft.Json.xml
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\Renci.SshNet.xml
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.csproj.ResolveComReference.cache
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.납품관리.Bring_Back.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.납품관리.Order_Send_Chk.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.마크.Check_ISBN_Sub.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\ExcelTest.findNchange.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\ExcelTest.Helper008.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.마크.Job_Order.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.마크.Mac_Chack_Up.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.마크.Mac_List_Merge.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.마크.Make_Document.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\ExcelTest.Marc.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.마크.Search_Infor_Sub.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Bill_manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Purchase_Aggregation.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Purchase_Book.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Purchase_Input.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Remit_reg2.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.회계.Sales_Book.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Sales_Lookup.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Sales_Deposit.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.회계.Sales_Detail.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Sales_Input.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Purchase_not_pay.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Remit_reg.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.회계.Sales_In_Pay.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Account.Sales_Not_Pay.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Board.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Calendar.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Quick_menu.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Sale_End.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Sale_Member_Manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Sale_Sale.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Sale_Settlement.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Send_Notice.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Convenience.Talk.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Book_Lookup.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Commodity_Edit.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Commodity_Morge.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Commodity_registration.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Commodity_Search.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Input_Lookup_Stock.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.List_aggregation.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.List_Chk_Work.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.List_Lookup.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Order_input.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Order_input_Search.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Delivery.Purchase.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.DLS.Input_DLS.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.DLS.School_Lookup.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.Book_infor_manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.ledger_of_use.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.memo.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.Order_manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.pw_change.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.Transaction_manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.Home_User_manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.User_Infor.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home._Sub_Search_Form.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.All_Book_manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Check_copy.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Check_ISBN.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Collect_Mac.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.DLS_Copy.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Mac_Output.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Equip_manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Mac_Stat.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Mac_Input.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Mac_List.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Nonverbal.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Search_Infor.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Set_Macro.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Setup_Shortcut.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Symbol_Add.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.Batch_processing.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac_Setting.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Notice_Send.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Sales_Details.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.User_account_inquiry.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.User_manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.login.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Main.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Work_log.Work_Log.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Home.Home_User_manage.ko.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.csproj.GenerateResource.cache
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.csproj.CoreCompileInputs.cache
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.csproj.CopyComplete
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.Properties.Resources.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\UniMarc.exe.config
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\UniMarc.exe
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\UniMarc.pdb
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\ko\UniMarc.resources.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\ko\UniMarc.resources.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.exe
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.pdb
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_memo.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Zoom_Picture.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Check_ISBN_Yes24.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.회계.Part_time.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.All_Book_manage_Add.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.All_Book_manage_Edit.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.All_Book_Detail.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_Preview.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Check_ISBN_Split.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Mac_Stat_Stat.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\WindowsFormsApp1.Mac.Marc_Plan.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_Plan_Sub_SelectList.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_mkList.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_Plan_Sub_SelectList_Edit.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_Plan_Sub_MarcEdit.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Check_Copy_Sub_Search.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Check_Copy_Sub_List.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\Interop.SHDocVw.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.DLS_Manage.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.Marc_FillBlank.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_Plan_Sub_SelectList_Morge.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.MarcCopySelect.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Mac_List_Add.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_Plan_PrintLabel.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.ShowDeleteMarc.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Check_Copy_Login.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.AddMarc.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.CD_LP.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.CD_LP_Sub.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.CD_LP_SelectList.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Help_007.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Help_008.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.CD_LP_List.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.AddMarc_FillBlank.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_Plan_ClassSymbol.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.Marc_Plan_GearExcel.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.마크.CD_LP_AddList.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\ArLog.Net4.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.From_User_manage_List.resources
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\System.Memory.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\System.Numerics.Vectors.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\arControl.Net4.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\CarlosAg.ExcelXmlWriter.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\bin\Debug\arCommUtil.dll
C:\Users\Administrator\Desktop\unimarc\unimarc\UniMarc\obj\Debug\UniMarc.csproj.AssemblyReference.cache

Some files were not shown because too many files have changed in this diff Show More