565 lines
15 KiB
Markdown
565 lines
15 KiB
Markdown
# 웹 페이지 통합 가이드
|
|
|
|
이 문서는 https://logistream.kpslp.kr 웹사이트에서 모바일 앱과 통신하는 방법을 설명합니다.
|
|
|
|
## 개요
|
|
|
|
모바일 앱은 웹뷰를 통해 웹사이트를 표시하며, JavaScript 브릿지를 통해 양방향 통신이 가능합니다.
|
|
|
|
## 1. 네이티브 기능 사용하기
|
|
|
|
### 1.1 로그인 성공 알림
|
|
|
|
사용자가 로그인에 성공하면 다음 함수를 호출하여 앱에 알립니다:
|
|
|
|
```javascript
|
|
// 로그인 API 응답 후
|
|
fetch('/api/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username, password })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// 네이티브 앱에 로그인 성공 알림
|
|
if (window.notifyLoginSuccess) {
|
|
window.notifyLoginSuccess({
|
|
userId: data.user.id,
|
|
userName: data.user.name,
|
|
userEmail: data.user.email,
|
|
token: data.token // JWT 토큰
|
|
});
|
|
}
|
|
|
|
// 위치 추적 자동 시작
|
|
if (window.startLocationTracking) {
|
|
window.startLocationTracking();
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### 1.2 로그아웃 알림
|
|
|
|
```javascript
|
|
function handleLogout() {
|
|
// 로그아웃 처리
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
|
|
// 네이티브 앱에 로그아웃 알림
|
|
if (window.notifyLogout) {
|
|
window.notifyLogout();
|
|
}
|
|
|
|
// 로그인 페이지로 리다이렉트
|
|
window.location.href = '/login';
|
|
}
|
|
```
|
|
|
|
### 1.3 현재 위치 요청
|
|
|
|
```javascript
|
|
// 현재 위치가 필요한 경우
|
|
function getCurrentLocation() {
|
|
return new Promise((resolve, reject) => {
|
|
// 네이티브 메시지 리스너 등록
|
|
const handler = (event) => {
|
|
if (event.detail.type === 'LOCATION_UPDATE') {
|
|
window.removeEventListener('nativeMessage', handler);
|
|
resolve(event.detail.location);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('nativeMessage', handler);
|
|
|
|
// 위치 요청
|
|
if (window.requestCurrentLocation) {
|
|
window.requestCurrentLocation();
|
|
} else {
|
|
// 웹 브라우저에서 실행 중인 경우 fallback
|
|
navigator.geolocation.getCurrentPosition(
|
|
(position) => {
|
|
window.removeEventListener('nativeMessage', handler);
|
|
resolve({
|
|
latitude: position.coords.latitude,
|
|
longitude: position.coords.longitude,
|
|
accuracy: position.coords.accuracy
|
|
});
|
|
},
|
|
(error) => {
|
|
window.removeEventListener('nativeMessage', handler);
|
|
reject(error);
|
|
}
|
|
);
|
|
}
|
|
|
|
// 타임아웃 설정 (5초)
|
|
setTimeout(() => {
|
|
window.removeEventListener('nativeMessage', handler);
|
|
reject(new Error('위치 요청 타임아웃'));
|
|
}, 5000);
|
|
});
|
|
}
|
|
|
|
// 사용 예시
|
|
async function showCurrentLocation() {
|
|
try {
|
|
const location = await getCurrentLocation();
|
|
console.log('현재 위치:', location);
|
|
// 지도에 표시 등
|
|
displayOnMap(location.latitude, location.longitude);
|
|
} catch (error) {
|
|
console.error('위치 가져오기 실패:', error);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 1.4 위치 추적 제어
|
|
|
|
```javascript
|
|
// 위치 추적 시작
|
|
function startTracking() {
|
|
if (window.startLocationTracking) {
|
|
window.startLocationTracking();
|
|
console.log('위치 추적 시작');
|
|
}
|
|
}
|
|
|
|
// 위치 추적 중지
|
|
function stopTracking() {
|
|
if (window.stopLocationTracking) {
|
|
window.stopLocationTracking();
|
|
console.log('위치 추적 중지');
|
|
}
|
|
}
|
|
|
|
// UI 토글 버튼 예시
|
|
const trackingButton = document.getElementById('tracking-toggle');
|
|
let isTracking = false;
|
|
|
|
trackingButton.addEventListener('click', () => {
|
|
if (isTracking) {
|
|
stopTracking();
|
|
trackingButton.textContent = '추적 시작';
|
|
} else {
|
|
startTracking();
|
|
trackingButton.textContent = '추적 중지';
|
|
}
|
|
isTracking = !isTracking;
|
|
});
|
|
```
|
|
|
|
## 2. 네이티브 메시지 수신
|
|
|
|
### 2.1 메시지 리스너 설정
|
|
|
|
```javascript
|
|
// 페이지 로드 시 리스너 등록
|
|
window.addEventListener('nativeMessage', function(event) {
|
|
const data = event.detail;
|
|
console.log('네이티브 메시지 수신:', data);
|
|
|
|
switch(data.type) {
|
|
case 'NATIVE_INFO':
|
|
handleNativeInfo(data);
|
|
break;
|
|
|
|
case 'LOCATION_UPDATE':
|
|
handleLocationUpdate(data.location);
|
|
break;
|
|
|
|
default:
|
|
console.log('알 수 없는 메시지 타입:', data.type);
|
|
}
|
|
});
|
|
```
|
|
|
|
### 2.2 네이티브 정보 처리
|
|
|
|
```javascript
|
|
function handleNativeInfo(data) {
|
|
console.log('플랫폼:', data.platform); // 'android' 또는 'ios'
|
|
console.log('버전:', data.version);
|
|
console.log('위치 권한:', data.hasLocationPermission);
|
|
|
|
// 플랫폼별 UI 조정
|
|
if (data.platform === 'ios') {
|
|
document.body.classList.add('platform-ios');
|
|
} else if (data.platform === 'android') {
|
|
document.body.classList.add('platform-android');
|
|
}
|
|
|
|
// 위치 권한이 없는 경우 안내
|
|
if (!data.hasLocationPermission) {
|
|
showAlert('위치 권한이 필요합니다. 설정에서 권한을 허용해주세요.');
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2.3 위치 업데이트 처리
|
|
|
|
```javascript
|
|
function handleLocationUpdate(location) {
|
|
console.log('위치 업데이트:', location);
|
|
|
|
// 지도에 현재 위치 표시
|
|
updateMapMarker(location.latitude, location.longitude);
|
|
|
|
// UI 업데이트
|
|
document.getElementById('latitude').textContent = location.latitude.toFixed(6);
|
|
document.getElementById('longitude').textContent = location.longitude.toFixed(6);
|
|
document.getElementById('accuracy').textContent = location.accuracy.toFixed(2) + 'm';
|
|
|
|
if (location.speed !== null) {
|
|
document.getElementById('speed').textContent = (location.speed * 3.6).toFixed(1) + 'km/h';
|
|
}
|
|
}
|
|
```
|
|
|
|
## 3. 환경 감지
|
|
|
|
### 3.1 모바일 앱 환경 확인
|
|
|
|
```javascript
|
|
// 모바일 앱에서 실행 중인지 확인
|
|
function isRunningInApp() {
|
|
return typeof window.ReactNativeWebView !== 'undefined';
|
|
}
|
|
|
|
// 사용 예시
|
|
if (isRunningInApp()) {
|
|
console.log('모바일 앱에서 실행 중');
|
|
// 앱 전용 기능 활성화
|
|
enableAppFeatures();
|
|
} else {
|
|
console.log('웹 브라우저에서 실행 중');
|
|
// 웹 전용 기능 활성화
|
|
enableWebFeatures();
|
|
}
|
|
```
|
|
|
|
### 3.2 플랫폼별 처리
|
|
|
|
```javascript
|
|
let currentPlatform = 'web';
|
|
|
|
window.addEventListener('nativeMessage', function(event) {
|
|
if (event.detail.type === 'NATIVE_INFO') {
|
|
currentPlatform = event.detail.platform;
|
|
}
|
|
});
|
|
|
|
function getPlatform() {
|
|
return currentPlatform;
|
|
}
|
|
|
|
// 사용 예시
|
|
if (getPlatform() === 'ios') {
|
|
// iOS 전용 스타일 적용
|
|
applyIOSStyles();
|
|
} else if (getPlatform() === 'android') {
|
|
// Android 전용 스타일 적용
|
|
applyAndroidStyles();
|
|
}
|
|
```
|
|
|
|
## 4. 완전한 통합 예제
|
|
|
|
### 4.1 로그인 페이지
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>로그인</title>
|
|
</head>
|
|
<body>
|
|
<form id="login-form">
|
|
<input type="text" id="username" placeholder="아이디" required>
|
|
<input type="password" id="password" placeholder="비밀번호" required>
|
|
<button type="submit">로그인</button>
|
|
</form>
|
|
|
|
<script>
|
|
document.getElementById('login-form').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
const username = document.getElementById('username').value;
|
|
const password = document.getElementById('password').value;
|
|
|
|
try {
|
|
const response = await fetch('/api/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
// 로컬 스토리지에 저장
|
|
localStorage.setItem('user', JSON.stringify(data.user));
|
|
localStorage.setItem('token', data.token);
|
|
|
|
// 네이티브 앱에 알림
|
|
if (window.notifyLoginSuccess) {
|
|
window.notifyLoginSuccess({
|
|
userId: data.user.id,
|
|
userName: data.user.name,
|
|
userEmail: data.user.email,
|
|
token: data.token
|
|
});
|
|
}
|
|
|
|
// 위치 추적 시작
|
|
if (window.startLocationTracking) {
|
|
window.startLocationTracking();
|
|
}
|
|
|
|
// 메인 페이지로 이동
|
|
window.location.href = '/dashboard';
|
|
} else {
|
|
alert('로그인 실패: ' + data.message);
|
|
}
|
|
} catch (error) {
|
|
console.error('로그인 오류:', error);
|
|
alert('로그인 중 오류가 발생했습니다.');
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### 4.2 대시보드 페이지
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>차량 위치 추적</title>
|
|
<style>
|
|
.location-info {
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
border-radius: 8px;
|
|
margin: 10px 0;
|
|
}
|
|
.tracking-controls {
|
|
margin: 20px 0;
|
|
}
|
|
.btn {
|
|
padding: 10px 20px;
|
|
margin: 5px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
.btn-primary { background: #007bff; color: white; }
|
|
.btn-danger { background: #dc3545; color: white; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>차량 위치 추적 시스템</h1>
|
|
|
|
<div class="location-info">
|
|
<h2>현재 위치</h2>
|
|
<p>위도: <span id="latitude">-</span></p>
|
|
<p>경도: <span id="longitude">-</span></p>
|
|
<p>정확도: <span id="accuracy">-</span></p>
|
|
<p>속도: <span id="speed">-</span></p>
|
|
</div>
|
|
|
|
<div class="tracking-controls">
|
|
<button id="get-location" class="btn btn-primary">현재 위치 가져오기</button>
|
|
<button id="start-tracking" class="btn btn-primary">추적 시작</button>
|
|
<button id="stop-tracking" class="btn btn-danger">추적 중지</button>
|
|
<button id="logout" class="btn btn-danger">로그아웃</button>
|
|
</div>
|
|
|
|
<script>
|
|
// 네이티브 메시지 리스너
|
|
window.addEventListener('nativeMessage', function(event) {
|
|
const data = event.detail;
|
|
|
|
if (data.type === 'LOCATION_UPDATE') {
|
|
updateLocationDisplay(data.location);
|
|
} else if (data.type === 'NATIVE_INFO') {
|
|
console.log('앱 정보:', data);
|
|
}
|
|
});
|
|
|
|
// 위치 정보 표시 업데이트
|
|
function updateLocationDisplay(location) {
|
|
document.getElementById('latitude').textContent = location.latitude.toFixed(6);
|
|
document.getElementById('longitude').textContent = location.longitude.toFixed(6);
|
|
document.getElementById('accuracy').textContent = location.accuracy.toFixed(2) + 'm';
|
|
|
|
if (location.speed !== null && location.speed > 0) {
|
|
document.getElementById('speed').textContent = (location.speed * 3.6).toFixed(1) + 'km/h';
|
|
} else {
|
|
document.getElementById('speed').textContent = '0 km/h';
|
|
}
|
|
}
|
|
|
|
// 현재 위치 가져오기 버튼
|
|
document.getElementById('get-location').addEventListener('click', () => {
|
|
if (window.requestCurrentLocation) {
|
|
window.requestCurrentLocation();
|
|
} else {
|
|
alert('네이티브 앱에서만 사용 가능합니다.');
|
|
}
|
|
});
|
|
|
|
// 추적 시작 버튼
|
|
document.getElementById('start-tracking').addEventListener('click', () => {
|
|
if (window.startLocationTracking) {
|
|
window.startLocationTracking();
|
|
alert('위치 추적을 시작했습니다.');
|
|
} else {
|
|
alert('네이티브 앱에서만 사용 가능합니다.');
|
|
}
|
|
});
|
|
|
|
// 추적 중지 버튼
|
|
document.getElementById('stop-tracking').addEventListener('click', () => {
|
|
if (window.stopLocationTracking) {
|
|
window.stopLocationTracking();
|
|
alert('위치 추적을 중지했습니다.');
|
|
} else {
|
|
alert('네이티브 앱에서만 사용 가능합니다.');
|
|
}
|
|
});
|
|
|
|
// 로그아웃 버튼
|
|
document.getElementById('logout').addEventListener('click', async () => {
|
|
try {
|
|
// 서버에 로그아웃 요청
|
|
await fetch('/api/logout', { method: 'POST' });
|
|
|
|
// 로컬 데이터 삭제
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
|
|
// 네이티브 앱에 알림
|
|
if (window.notifyLogout) {
|
|
window.notifyLogout();
|
|
}
|
|
|
|
// 로그인 페이지로 이동
|
|
window.location.href = '/login';
|
|
} catch (error) {
|
|
console.error('로그아웃 오류:', error);
|
|
}
|
|
});
|
|
|
|
// 페이지 로드 시 저장된 사용자 정보 확인
|
|
window.addEventListener('load', () => {
|
|
const user = localStorage.getItem('user');
|
|
if (!user) {
|
|
window.location.href = '/login';
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
## 5. 서버 API 구현 예시
|
|
|
|
### 5.1 위치 정보 수신 API (Node.js/Express)
|
|
|
|
```javascript
|
|
// routes/location.js
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
|
|
// 위치 정보 수신 엔드포인트
|
|
router.post('/api/location', async (req, res) => {
|
|
try {
|
|
const {
|
|
userId,
|
|
userName,
|
|
userEmail,
|
|
location,
|
|
deviceInfo
|
|
} = req.body;
|
|
|
|
// 데이터 검증
|
|
if (!userId || !location || !location.latitude || !location.longitude) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: '필수 데이터가 누락되었습니다.'
|
|
});
|
|
}
|
|
|
|
// 데이터베이스에 저장
|
|
const locationRecord = await db.locations.create({
|
|
user_id: userId,
|
|
user_name: userName,
|
|
user_email: userEmail,
|
|
latitude: location.latitude,
|
|
longitude: location.longitude,
|
|
accuracy: location.accuracy,
|
|
altitude: location.altitude,
|
|
heading: location.heading,
|
|
speed: location.speed,
|
|
platform: deviceInfo.platform,
|
|
platform_version: deviceInfo.version,
|
|
timestamp: new Date(location.timestamp),
|
|
created_at: new Date()
|
|
});
|
|
|
|
console.log(`위치 업데이트: ${userName} (${userId}) - ${location.latitude}, ${location.longitude}`);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '위치 정보가 저장되었습니다.',
|
|
id: locationRecord.id
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('위치 저장 오류:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '서버 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|
|
```
|
|
|
|
## 6. 주의사항
|
|
|
|
1. **보안**: JWT 토큰은 안전하게 저장하고 전송해야 합니다.
|
|
2. **배터리**: 위치 추적은 배터리를 많이 소모하므로 필요할 때만 활성화하세요.
|
|
3. **권한**: 사용자에게 위치 권한의 필요성을 명확히 설명하세요.
|
|
4. **오류 처리**: 네트워크 오류나 권한 거부 상황을 적절히 처리하세요.
|
|
5. **Fallback**: 웹 브라우저에서도 작동할 수 있도록 fallback 로직을 구현하세요.
|
|
|
|
## 7. 디버깅
|
|
|
|
### 7.1 웹뷰 디버깅 (Android)
|
|
|
|
```bash
|
|
# Chrome에서 chrome://inspect 접속
|
|
# 연결된 디바이스의 웹뷰 선택
|
|
```
|
|
|
|
### 7.2 웹뷰 디버깅 (iOS)
|
|
|
|
```
|
|
Safari > 개발 > [디바이스 이름] > [앱 이름]
|
|
```
|
|
|
|
### 7.3 로그 확인
|
|
|
|
```javascript
|
|
// 콘솔 로그는 네이티브 디버거에서 확인 가능
|
|
console.log('디버그 메시지');
|
|
console.error('오류 메시지');
|
|
```
|
|
|