LifeSports Application(ReactNative & Nest.js) - 14. API Gatewayバインド、map-service(2)
32281 ワード
#1クライアント、API Gatewayのバインド
前回の記事でKongを用いてapiゲートウェイを実現した.そして、このAPIサーバとREACTネイティブクライアントを1つのポートとして複数のサービスとの通信を実現します.
reactネイティブパッケージ.jsonのproxyを次のように変更します.
{
"scripts": {
"android": "react-native run-android adb reverse tcp:8081 tcp:8000",
...
},
"proxy": "http://10.0.2.2:8000"
}
次にapiディレクトリのファイルを変更します.import client from './client';
import AsyncStorage from '@react-native-async-storage/async-storage';
export const login = ({
email,
password
}) => client.post('http://10.0.2.2:8000/auth-service/login', {
email,
password
});
export const register = ({
email,
password,
phoneNumber,
nickname
}) => client.post('http://10.0.2.2:8000/auth-service/register', {
email,
password,
phoneNumber,
nickname
});
export const getUser = async userId => client.get(`http://10.0.2.2:8000/auth-service/${userId}`, {
headers: {
'Authorization': 'Bearer ' + JSON.parse(await AsyncStorage.getItem('token'))
}
});
export const check = async userId => client.get(`http://10.0.2.2:8000/auth-service/${userId}/check`, {
headers: {
'Authorization': 'Bearer ' + JSON.parse(await AsyncStorage.getItem('token'))
}
});
export const logout = () => client.post('http://10.0.2.2:8000/auth-service/logout');
tokenフィールドをauthサービスの応答ユーザーに追加し、AppControllerログイン時に返される値を変更します.import { Body, Controller, Get, HttpStatus, Param, Post, UseGuards } from "@nestjs/common";
import { Builder } from "builder-pattern";
import { AuthService } from "./auth/auth.service";
import { statusConstants } from "./constants/status.constant";
import { UserDto } from "./dto/user.dto";
import { JwtAuthGuard } from "./guard/jwt-auth.guard";
import { LocalAuthGuard } from "./guard/local-auth.guard";
import { UserService } from "./user/user.service";
import { RequestLogin } from "./vo/request.login";
import { RequestRegister } from "./vo/request.register";
import { ResponseUser } from "./vo/response.user";
@Controller('auth-service')
export class AppController {
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {}
...
@UseGuards(LocalAuthGuard)
@Post('login')
public async login(@Body() requestLogin: RequestLogin): Promise<any> {
try {
const result = await this.authService.login(Builder(UserDto).email(requestLogin.email)
.build());
if(result.status === statusConstants.ERROR) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Error message: " + result.message,
});
}
return await Object.assign({
status: HttpStatus.OK,
payload: Builder(ResponseUser).email(result.payload.email)
.nickname(result.payload.nickname)
.phoneNumber(result.payload.phoneNumber)
.userId(result.payload.userId)
.token(result.access_token)
.build(),
message: 'Successfully Login'
});
} catch(err) {
return await Object.assign({
status: statusConstants.ERROR,
payload: null,
message: "Error message: " + err
});
}
}
...
}
react nativeを実行してテストしましょう.#2 auth-service連携テスト
結果画面と同様に、ログインに成功したことを確認できます.では、前に作ったmap-serviceもこのように連動してmapデータを受信することができるでしょう.
map-service、wayfing-serviceのバックエンドが完了し、クライアントにmapに関連する冗長モジュールを作成させます.
#3marker,mapredux
mapのmarkerは、検索するデータがどこにあるかを示す高可視性の要素です.管理アプリケーションでmarkerは、次の機能を実行します.
1)タグをクリックすると、下部に場所に関する模式図が作成されます.
2)情報図は場所情報を含み、「拡大」ボタンがある.
3)「拡大」ボタンをクリックすると、詳細、拡大関連機能を実行できます.
上記の機能を実現するには、まず人口図形を作成します.したがって、タグをクリックすると、冗長に作成する表示フラグのステータス値にtrueとfalseが追加され、trueの写真の高さが30%になり、マッピングのサイズが60%になります.
また、mapでタグに使用されるマッピングデータはリスト全体にインポートされ、タグごとにデータが付与されるため、map redoxを実装してmap情報を含む.
まずmarkerを作成し、infoグラフィックの作成から実装します.
import { createAction, handleActions } from "redux-actions";
const CHANGE_VISIBLE = 'marker/VISIBLE';
export const changeState = createAction(
CHANGE_VISIBLE,
value => value
);
const initialState = {
visible: null
};
const marker = handleActions(
{
[CHANGE_VISIBLE]: (state, { payload: value }) => ({
...state,
visible: value
}),
},
initialState,
);
export default marker;
import { createAction, handleActions } from "redux-actions";
const [CHANGE_MAP] = 'map/CHANGE_MAP';
export const changeMap = createAction(
CHANGE_MAP,
value => value
);
const initialState = {
map: null,
error: null,
};
const map = handleActions(
{
[CHANGE_MAP]: (state, { payload: map }) => ({
...state,
map,
}),
},
initialState,
);
export default map;
import { combineReducers } from "redux";
import { all } from "redux-saga/effects";
import auth, { authSaga } from './auth';
import loading from "./loading";
import user, { userSaga } from "./user";
import marker from './marker';
import map from "./map";
const rootReducer = combineReducers({
auth,
loading,
user,
marker,
map,
});
export function* rootSaga() {
yield all([
authSaga(),
userSaga(),
]);
};
export default rootReducer;
visible値をredus state値に設定してmarkerをクリックすると、visibleでtrueをクリックし、ボタンを閉じるときにfalse値を与えます.次に、マッピング中にタグに存在するマッピングデータに変更します.import React from "react"
import { useDispatch } from 'react-redux';
import { View } from "react-native";
import { Marker } from "react-native-nmap";
import markerImage from '../../../assets/img/markerImage.png';
import palette from "../../../styles/palette";
import { changeState } from "../../../modules/marker";
import { changeMap } from "../../../modules/map";
const CustomMarker = ({ data }) => {
const dispatch = useDispatch();
const coordinate = {
latitude: data.ycode,
longitude: data.xcode,
};
const onVisible = e => {
e.preventDefault();
dispatch(changeState(true));
dispatch(changeMap(data));
};
return(
<View>
<Marker coordinate={ coordinate }
image={ markerImage }
pinColor={ palette.blue[4] }
caption={{
text: data.nm,
textSize: 13,
}}
onClick={ onVisible }
/>
</View>
);
};
export default CustomMarker;
import React from 'react';
import { StyleSheet } from 'react-native';
import Loading from '../../../styles/common/Loading';
import NaverMapView from 'react-native-nmap';
import CustomMarker from './CustomMarker';
import { useSelector } from 'react-redux';
const NaverMap = () => {
const { visible } = useSelector(({ marker }) => ({ visible: marker.visible }));
const defaultLocation = {
latitude: 37.6009735,
longitude: 126.9484764
};
const dummyData = [
{"ycode":37.6144169,"type_nm":"구기체육관","gu_nm":"중랑구","parking_lot":"주차 가능(일반 18면 / 장애인 2면)","bigo":"","xcode":127.0842018,"tel":"949-5577","addr":"중랑구 숙선옹주로 66","in_out":"실내","home_page":"http://www.jungnangimc.or.kr/","edu_yn":"유","nm":"묵동다목적체육관"},
{"ycode":37.573171,"type_nm":"골프연습장","gu_nm":"중랑구","parking_lot":"용마폭포공원 주차장 이용(시간당 1,200원 / 5분당 100원)","bigo":"","xcode":127.0858392,"tel":"490-0114 ","addr":"중랑구 용마산로 217","in_out":"실내","home_page":"http://www.jjang.or.kr/jjang/","edu_yn":"유","nm":"중랑청소년수련관 골프연습장"},
{"ycode":37.580646,"type_nm":"수영장","gu_nm":"중랑구","parking_lot":"홈플러스 면목점 B동 이용, 겸재로 2길 거주자 우선 주차 구역 이용(36면) ","bigo":"","xcode":127.0773483,"tel":"435-0990","addr":"중랑구 면목동 중랑천 장안교 ","in_out":"실외","home_page":"http://jungnangimc.or.kr/","edu_yn":"무","nm":"중랑천물놀이시설"},
{"ycode":37.6058844,"type_nm":"수영장","gu_nm":"중랑구","parking_lot":"주차 가능","bigo":"","xcode":127.1088479,"tel":"492-7942","addr":"중랑구 송림길 156","in_out":"실내","home_page":"http://mangwoo.kr/","edu_yn":"유","nm":"망우청소년수련관 수영장"},
{"ycode":37.5792399,"type_nm":"수영장","gu_nm":"중랑구","parking_lot":"주차 가능(51면)","bigo":"","xcode":127.0959499,"tel":"436-9200","addr":"중랑구 사가정로72길 47","in_out":"실내","home_page":"http://jungnangspo.seoul.kr","edu_yn":"유","nm":"중랑문화체육관 수영장"},
{"ycode":37.6151721,"type_nm":"수영장","gu_nm":"중랑구","parking_lot":"주차 가능(36면)","bigo":"","xcode":127.0874763,"tel":"3423-1070","addr":"중랑구 신내로15길 189","in_out":"실내","home_page":"http://jungnangspo.seoul.kr","edu_yn":"유","nm":"중랑구민체육센터 수영장"},
{"ycode":37.573171,"type_nm":"생활체육관","gu_nm":"중랑구","parking_lot":"용마폭포공원 주차장 이용(시간당 1,200원 / 5분당 100원)","bigo":"","xcode":127.0858392,"tel":"490-0114 ","addr":"중랑구 용마산로 217","in_out":"실내","home_page":"http://www.jjang.or.kr/jjang/","edu_yn":"유","nm":"중랑청소년수련관"},
{"ycode":37.6058844,"type_nm":"생활체육관","gu_nm":"중랑구","parking_lot":"주차 가능","bigo":"","xcode":127.1088479,"tel":"492-7942","addr":"중랑구 송림길 156","in_out":"실내","home_page":"http://http://mangwoo.kr/","edu_yn":"유","nm":"망우청소년수련관"},
{"ycode":37.5878763,"type_nm":"생활체육관","gu_nm":"중랑구","parking_lot":"공영주차장 회원 2시간 무료","bigo":"","xcode":127.0808914,"tel":"495-5200","addr":"중랑구 겸재로 23길 27","in_out":"실내","home_page":"http://jungnangspo.seoul.kr","edu_yn":"유","nm":"면목2동체육관"},
];
return(
<NaverMapView style={
visible ?
styles.openInfoContainer :
styles.closeInfoContainer
}
showsMyLocationButton={ true }
center={{
...defaultLocation,
zoom: 15,
}}
scaleBar={ true }
>
{
dummyData ?
dummyData.map(
(map, i) => {
return <CustomMarker key={ i }
data={ map }
/>
}
) : <Loading />
}
</NaverMapView>
);
};
const styles = StyleSheet.create({
openInfoContainer: {
width: '100%',
height: '60%'
},
closeInfoContainer: {
width: '100%',
height: '90%',
},
});
export default NaverMap;
markerが完了した以上、infoグラフィックを実装し、markerをクリックすると良好なinfoグラフィックが生成されるかどうかをテストします.import React from "react"
import { StyleSheet, Text, TouchableOpacity, View } from "react-native"
import Icon from 'react-native-vector-icons/Ionicons';
import { useDispatch, useSelector } from "react-redux";
import { changeState } from "../../../modules/marker";
import palette from "../../../styles/palette";
const InfoClose = () => {
const { map } = useSelector(({ map }) => ({ map: map.map }));
const dispatch = useDispatch();
const onClose = e => {
e.preventDefault();
dispatch(changeState(false));
};
return (
<View style={ styles.container } >
<View style={ styles.type_article }>
<Text style={ styles.type_font }>
{ map.type_nm }
</Text>
</View>
<TouchableOpacity onPress={ onClose } >
<Icon name={ 'ios-close-sharp' }
size={ 25 }
color={ palette.blue[4] }
/>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'flex-end',
width: '100%',
height: '15%',
marginTop: 10,
paddingRight: 10,
},
type_article: {
width: '88%',
marginLeft: 15,
},
type_font: {
fontWeight: 'bold'
},
});
export default InfoClose;
ボタンを閉じる構成部品.import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { useSelector } from "react-redux";
import palette from "../../../styles/palette";
const Info = () => {
const { map } = useSelector(({ map }) => ({ map: map.map }));
return(
<View style={ styles.container } >
<View style={ styles.title } >
<Text style={ styles.place_name } >
{ map.nm }
</Text>
</View>
<View style={ styles.address } >
<Text>
{ map.addr }
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
width: '100%',
height: '45%',
borderBottomColor: palette.gray[3],
borderBottomWidth: 1,
},
title: {
flexDirection: 'column',
justifyContent: 'flex-start',
margin: 15,
},
place_name: {
fontWeight: 'bold',
fontSize: 20,
},
address: {
justifyContent: 'flex-start',
marginLeft: 15,
},
});
export default Info;
入力図面に簡略化された情報を表示する構成部品.import React from "react";
import { useNavigation } from '@react-navigation/native';
import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
import palette from "../../../styles/palette";
const InfoRental = () => {
const navigation = useNavigation();
const onRental = state => {
// navigation.navigate("Rental", {
// name: "Rental",
// data: state.map
// })
};
return(
<View style={ styles.container } >
<TouchableOpacity style={ styles.rental_button }
onPress={ onRental }
>
<Text style={ styles.rental_text } >
대관하기
</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'center',
width: '100%',
height: '15%',
marginTop: 13,
paddingRight: 10
},
rental_button: {
alignItems: 'center',
justifyContent: 'center',
width: 100,
height: 30,
borderColor: palette.blue[4],
borderWidth: 3,
borderRadius: 30
},
rental_text: {
fontWeight: 'bold'
}
});
export default InfoRental;
[拡張](Extend)ボタンに使用される構成部品.import React from "react";
import { StyleSheet, View } from "react-native";
import palette from "../../../styles/palette";
import InfoClose from "./InfoClose";
import Info from "./Info";
import InfoRental from "./InfoRental";
const MapFooter = () => {
return(
<View style={ styles.container }>
<InfoClose />
<Info />
<InfoRental />
</View>
);
};
const styles = StyleSheet.create({
container: {
width: '100%',
height: '30%',
backgroundColor: palette.white[0],
}
});
export default MapFooter;
情報グラフィックを集約する構成部品.import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { useSelector } from 'react-redux';
import MapFooter from './components/MapFooter';
import MapHeader from './components/MapHeader';
import NaverMap from './components/NaverMap';
const MapScreen = () => {
const { visible } = useSelector(({ marker }) => ({ visible: marker.visible }));
return(
<View style={ styles.container }>
<MapHeader />
<NaverMap />
{
visible &&
<MapFooter />
}
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'column',
flex: 1,
},
});
export default MapScreen;
userSelectorを使用してredox state値にアクセスし、marker値を取得してMapFooterを表示するかどうかを決定します.ではテストを行いましょう#4テスト
総覧図にマークされた表示効果が表示されます.
タグをクリックすると、良好な入力グラフィックが作成され、ボタンを閉じると正常に動作します.
次の記事では、map全体のデータをインポートし、mapにデータを表示し、[スケール](Scale)ボタンを表示する詳細なページを作成します.
Reference
この問題について(LifeSports Application(ReactNative & Nest.js) - 14. API Gatewayバインド、map-service(2)), 我々は、より多くの情報をここで見つけました https://velog.io/@biuea/LifeSports-ApplicationReactNative-Nest.js-14.-API-gateway-연동-map-service2テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol