搜尋

搜尋結果

Spring Boot 05 - 為 http json api 加入登入要求
科技新知
MacauYeah・2024-07-02

本節,我們將為之前的http服務,加入認證機制,只有在資料庫現存的用戶可以登入及訪問我們的json api。 下戴模版 慣例,我們用Spring Initializr (Maven) 下載模版,Dependency主要選擇 Spring Web Spring Boot DevTools Spring Security Controller 跟上節一樣,我們起一個Controller,為簡化測試,我們只做http GET api。 由於本blog對於Source Code的顯示不太友好,有需要看source code的,請到Github查看 //src/main/java/io/github/macauyeah/springboot/tutorial/springbootwebapidata/controller/HomeController.java import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api") public class HomeController { @GetMapping("/someRecord/{uuid}") public Map readSomeRecord(@PathVariable String uuid) { return Map.of("ret", "your uuid:" + uuid); } } 準備我們的test case,但這次我們預期它應該要出現登入失敗的結果。 //src/test/java/io/github/macauyeah/springboot/tutorial/springbootwebapidata/controller/HomeControllerTest.java @SpringBootTest @AutoConfigureMockMvc public class HomeControllerTest { @Autowired private MockMvc mockMvc; @Test void testNoLogin() throws Exception { RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/api/someRecord/1234") .contentType(MediaType.APPLICATION_JSON); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().is4xxClientError()) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").doesNotExist()) .andDo(MockMvcResultHandlers.print()); } } 在我們執行上述的測試,test case 成功過了。我們的基本設定跟上一節其實沒有多大改動,為何現在http api會回傳狀態 401? 那是因為我們在依賴中加了,Spring Security,它配合了Spring Web,就會自動為所有api加入權限檢測。我們的測試中,沒有任何用戶登入,當然會出現 http 401。為了讓我們可以好好管理誰可以使用api,我們就來設定一定Security。 我們加一個WebSecurityConfig.java,暫時指定所有的訪問路徑都必需有USER權限,並且用 http basic的方式登入。 //src/main/java/io/github/macauyeah/springboot/tutorial/springbootwebapidata/config/WebSecurityConfig.java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class WebSecurityConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorizeHttpRequests -> { authorizeHttpRequests.requestMatchers("/**").hasRole("USER"); // 所有的訪問路徑都必需有USER權限 }); http.httpBasic(Customizer.withDefaults()); // 使用http basic作為登入認證的方式 return http.build(); } } 上述例子,只是擋了沒有權限的人,我們還需要讓有登入身份的用戶可以成得取限User權限。 我們繼續修改,WebSecurityConfig,加入只在記憶體有效的InMemoryUser import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; public class WebSecurityConfig { //.. @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); // 我們的密碼不應該明文儲,比較保險,我們使用BCrypt演算法,為密碼做單向加密。 } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withUsername("admin") .password(passwordEncoder().encode("pass")) .roles("USER").build(); // 我們在記憶中體,加入一個測試用的User,它的名字為admin,密碼為pass,權限為User return new InMemoryUserDetailsManager(user); } 然後加入新的測試,直接模擬Role。結果是通過的。 //src/test/java/io/github/macauyeah/springboot/tutorial/springbootwebapidata/controller/HomeControllerTest.java @Test void testLoginWithRoles() throws Exception { RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/api/someRecord/1234") .contentType(MediaType.APPLICATION_JSON).with( SecurityMockMvcRequestPostProcessors.user("someone") .roles("USER", "ADMIN")); // 沒有使用密碼,只使用Role this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").value("your uuid:1234")) .andDo(MockMvcResultHandlers.print()); } 再來一個測試,改用密碼登入,分別輸入錯的和正確的密碼。 @Test void testLoginWithWrongPasswordAndNoRole() throws Exception { RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/api/someRecord/1234") .header("Authorization", "Basic randompass") // 輸入錯的密碼,應該回傳http 401 Unauthorized .contentType(MediaType.APPLICATION_JSON); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().is4xxClientError()) .andDo(MockMvcResultHandlers.print()); } @Test void testLoginWithPassword() throws Exception { RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/api/someRecord/1234") .header("Authorization", "Basic YWRtaW46cGFzcw==") // http basic 就是把 admin:pass 轉成base64 .contentType(MediaType.APPLICATION_JSON); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().is2xxSuccessful()) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").value("your uuid:1234")) .andDo(MockMvcResultHandlers.print()); } 最後,當然是正確的密碼才能通過。若果大家還是半信半疑,我們可以跑起真的正服務(IDE RUN或mvn spring-boot:run),然後用curl去試。 curl http://localhost:8080/api/someRecord/1234 // failed with 401 curl -u "admin:pass" http://localhost:8080/api/someRecord/1234 // successed 使用SQL Database讀取用戶登入資訊 一般而言,我們不可能把所有用戶登資訊打在InMemoryUser中,通常背後有一個資料庫儲存所有的用戶資訊,我們在登入時,讀取它來做對比檢證。 為此,我們在maven中,加入 Spring Data JPA h2 database (或任何你的資料庫,如mysql 、 sql server) 最後一步,我們把InMemoryUser去掉,改為從資料庫讀取。因為原始碼太多,就不全部貼上。最主要的是WebSecurityConfig.java要關掉之前的UserDetailsService,改為提供一個UserServiceImpl類,它會實現UserDetailsService的功能。 @Configuration @EnableWebSecurity public class WebSecurityConfig { // 把原來的Bean先變成註解,其他不變 // @Bean // public UserDetailsService userDetailsService() { // UserDetails user = User.withUsername("admin") // .password(passwordEncoder().encode("pass")) // .roles("USER").build(); // return new InMemoryUserDetailsManager(user); // } } // spring-boot-tutorial/spring-boot-web-api-data/src/main/java/io/github/macauyeah/springboot/tutorial/springbootwebapidata/config/UserServiceImpl.java // other import import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; @Service public class UserServiceImpl implements UserDetailsService { @Autowired PasswordEncoder passwordEncoder; @Autowired UserRepo userRepo; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 因為我們資料庫沒有資料,為了方便測試密碼的加密,我們在java code上直接插入一筆資料。 UserEntity defaultUser = new UserEntity(); defaultUser.setUsername("admin"); defaultUser.setPassword(passwordEncoder.encode("pass")); defaultUser.setRole("USER"); defaultUser.setUuid(UUID.randomUUID().toString()); userRepo.save(defaultUser); // 上述為測試用插入資料,不應該出現在正式使用環境中。 UserEntity user = userRepo.findOneByUsername(username) .orElseThrow(() -> new UsernameNotFoundException(username + " not found")); // 找找資料庫有沒有正在登入的該名使用者username List authorities = List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole())); LOG.debug("got user uuid:{}, username:{}, role:{} from database", user.getUuid(), username, user.getRole()); // 如果前面的 findOneByUsername 有結果回傳,我們就給它一個ROLE_XXX的權限。 return new User(username, user.getPassword(), authorities); // 這裏從沒有檢查過密碼是否有匹配,全部交給Spring Security去做 } } //spring-boot-tutorial/spring-boot-web-api-data/src/main/java/io/github/macauyeah/springboot/tutorial/springbootwebapidata/entity/UserEntity.java // spring-boot-tutorial/spring-boot-web-api-data/src/main/java/io/github/macauyeah/springboot/tutorial/springbootwebapidata/repo/UserRepo.java 上述段落中,筆者省略了UserEntity和UserRepo,它們只是一般的spring-data-jpa概念,有需要可以經文末的連結查看完全原始碼。最需要注意的,是UserEntity的password欄位,在資料庫中是以加密的方式儲存。我們在配匹登入者與資料庫記錄時,也沒有自行檢驗密碼的需要。我們只是在加密過的密碼回傳給Spring Security,Spring框架會自行把登入者輸入的密碼與加密了的密碼作比較。

Spring Boot 04 - 進入http json api 世代
科技新知
MacauYeah・2024-05-23

本節,我們將會建立一個http服務,提供json api讓程式訪問。 下戴模版 我們跟上節一樣,使用Spring Initializr (Maven) 下載模版,但細節筆者就不再講啦。Dependency主要選擇 Spring Web Spring Boot DevTools 下載後,可以直接運行測試,可以用指令 mvn test 或經IDE運行。Spring會至少測試下能不能成功取用預設的8080端口。 Controller 我們若要實作 http json api,需要在 spring 中加入一個類,附註為 @RestController ,那方便起見,類名我們也命名為 XXXController 吧。作為示範,我們弄一個 HomeController.java ,裏面有最常見的 http GET, POST功能。 // src/main/java/io/github/macauyeah/springboot/tutorial/springbootwebapibasic/controller/HomeController.java import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; // ... other import @RestController @RequestMapping("/api") public class HomeController { @GetMapping("/someRecord/{uuid}") public Map readSomeRecord(@PathVariable String uuid) { return Map.of("ret", "your uuid:" + uuid); } @PostMapping("/someRecord") public Map createSomeRecord(@RequestBody Map requestBody) { HashMap ret = new HashMap(requestBody); ret.put("ret", "got your request"); return ret; } } HomeController裏,完整的URL 其實為: GET http://localhost:8080/api/someRecord/{uuid} POST http://localhost:8080/api/someRecord URL中的api之後的路徑,都是定義在 HomeController 中,而前半的8080及context path,是使用預設值。在正式環境下,可能隨時會被重新定義。但我們做本地測試,只需要驗證預設值就可以了。 我們真的運行起程式mvn clean compile spring-boot:run,再使用最簡測試工具進行測試。Windows的朋友,可以選擇Postman作為測試,它有圖形介面。而linux的朋友,請用curl,預設安裝都會有。下列為方便表示測試參數,筆者選用curl。 測試GET,其中1234會自動對應到spring裏的uuid。 curl http://localhost:8080/api/someRecord/1234 # return {"ret":"your uuid:1234"} 測試 POST,其中的 -d 參數,會對應 spring裏的 @RequestBody, -H 參數則是設定 http header 的意思,我們就使用約定俗成的 json 作為 header 。 curl -X POST http://localhost:8080/api/someRecord -H "Content-Type: application/json" -d '{"requst":"did you get it?"}' # return {"requst":"did you get it?","ret":"got your request"} 上面的兩個操作,都回傳了我們輸入的資訊,這代表了我們成功用spring架起了http json api,而且正常讀入資訊。 Test Case 雖然我們可以正常地架起 api,但每次開發都要 postman / curl這種工具額外試一次,其實也有一些成本。而且 api 數量變大,或經多次修改後,就重複人手執行,就變得相當討厭。 面對這個問題,筆者會建議寫測試用例,即是Test Case,而且用Spring內置的@SpringBootTest來寫。 產生一個空的Test類,vscode中,最簡單可以Source Action => Generate Test,然後加入這次要測試的參數。 // src/test/java/io/github/macauyeah/springboot/tutorial/springbootwebapibasic/controller/HomeControllerTest.java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @SpringBootTest @AutoConfigureMockMvc public class HomeControllerTest { @Autowired private MockMvc mockMvc; @Test void testGetSomeRecord() throws Exception { RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/api/someRecord/1234") .contentType(MediaType.APPLICATION_JSON); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").value("your uuid:1234")) .andDo(MockMvcResultHandlers.print()); } @Test void testPostSomeRecord() throws Exception { String request = """ {"requst":"did you get it?"} """; RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/api/someRecord") .contentType(MediaType.APPLICATION_JSON) .content(request); this.mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.jsonPath("$.requst").value("did you get it?")) .andExpect(MockMvcResultMatchers.jsonPath("$.ret").value("got your request")) .andDo(MockMvcResultHandlers.print()); } } 最後就是執行 mvn test 或經IDE運行,應該都會得到所有測試都通過的結果。 mvn test # other test result ... [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.368 s -- in io.github.macauyeah.springboot.tutorial.springbootwebapibasic.controller.HomeControllerTest # other test result ... 上面的程式碼很多,我們逐一來。 @SpringBootTest 寫在類的外面,代表執行這個測試類時,需要運行起整個Spring程序,當然也包括http的部份。 @AutoConfigureMockMvc 寫在類的外面,代表執行這個測試類時,可以模擬一些發向自己的 http 請求。 @Autowired private MockMvc mockMvc 寫在類的裏面,因為之前有定義了可以模擬 http 的請求,Spring在運行時為大家提供了那個所謂的模擬http client的實例。 MockMvcRequestBuilders,則是建造要測試的URL及Header參數。 MockMvcResultMatchers,則是檢查回傳的結果是否如遇期的一樣。 為何這個http client叫模擬 - Mock ? 因為在測試用例中,可能連Controller 內部依賴組件也需要進一步模擬,這樣才能把測試目標集中在Controller裏,這也是單元測試的原意。只是本次的例子看不出模擬與否的差別。 MockMvcResultMatchers.jsonPath(),這是用來檢測json的結構是否跟預期一樣。有些網路上的其他例子會簡寫成 jsonPath() ,但因為vscode IDE的自動import功能比較差,筆者還是保留傳統的寫法。 如果大家覺得@SpringBootTest很難,想折衷地把其他測試方法,那麼把 postman / curl好好管理起來,每次修改完程式,都完整地執行一次 postman / curl ,也可以達到測試的效果。只不過大家還是要好好學會整合 postman / curl,知道如何檢測json結構,什麼時候有錯,什麼時候叫測試通過,所以也要花一樣功夫來實現。 最後,大家千萬不要因為測試難寫而逃課,因為寫測試絕對地可以減輕日後重執行的工作量。除非你的程式碼即用即棄,否則都建議寫測試。(測試跟寫文檔不一樣,有了測試也不能沒有文檔。好消息的是,文檔現在越來越多自動生成的工具,我們日後再找機會介紹。) Source Code spring boot web api basic

出遊必備!全球機場WIFI密碼地圖
走遍世界
Jackst・2017-04-05

現代人去旅行最大的煩惱一定是網路問題,特別是晚上的飛機在機場買不到電話卡也連不到機場的網路,那種徬徨一定好多人試過。 世界上最遙遠的距離莫過於,「我看得到WI-FI信號,但係連唔到!」 小編最近發現了一個全球各個機場的Wifi地圖 - Wifox , 這個地圖已經超過全球150個機場的Wifi資料,更加邀請全球的用戶不停更新資料。 還可以匯出製作成離線地圖! 使用方法很簡單,只需要點擊地圖上所屬地方的藍色飛機,該地方的機場的Wi-Fi名稱、密碼和位置就會在畫面左面出現。 除了線上版本,Google My Maps 都可以匯出.kml 檔案,自製離線Wifi地圖,不能上線時亦可隨時查閱到! 另外WiFox也有手機App可以下載使用,只不過要付費才可以使用。 WiFox網站:https://www.google.com/maps/d/u/0/viewer?mid=1Z1dI8hoBZSJNWFx2xr_MMxSxSxY App store:https://itunes.apple.com/us/app/wifox/id1130542083?mt=8 Google play:https://play.google.com/store/apps/details?id=com.foxnomad.wifox.wifox&hl=en Amazon:https://www.amazon.com/foXnoMad-LLC-WiFox/dp/B01LWKMTG6 日韓台免費Wi-Fi攻略 雖然WiFox無亞洲地區的機場Wi-Fi攻略,但其實日韓台都有許多Wi-Fi熱點供旅客使用,只要你識用,同樣十分方便! 日本-Japan Connected-free Wi-Fi WiFox_機場_WiFi 更多 Japan Connected-free Wi-Fi是日本旅遊局開發的應用程式,所有功能供旅客免費使用,只要於應用程式內登錄一次就OK,不用每次登錄十分方便!它可以根據你目前所在地點,顯示出附近的Wi-Fi熱點,包括主要都市和車站周遭的Wi-Fi熱點等。應用程式內包括幾百個Wi-Fi熱點,有齊全日本的Wi-Fi熱點,不止機場可用,於電車、公共汽車、合作商店中都可使用。 圖片來源:Japan Connected-free Wi-Fi 更多 圖片來源:Japan Connected-free Wi-Fi 更多 Japan Connected-free Wi-Fi網址:http://www.ntt-bp.net/jcfw/tw.htmlAndroid : https://play.google.com/store/apps/details?id=com.nttbp.jfwiOS : https://itunes.apple.com/tw/app/japan-connected-free-wi-fi/id810838196?mt=8 韓國-Seoul Wifi WiFox_機場_WiFi8 更多 公共免費Wi-Fi分兩種一種是公共Wi-Fi(Public Wifi@Seoul),另一種是公共機構的Wi-Fi(Seoul_WiFi),在首爾市內有多達10000個以上的Wi-Fi熱點,記住這兩個WiFi名稱,在韓國隨時都上網! 圖片來源:Seoul Wifi 更多 圖片來源:Seoul Wifi 更多 Seoul Wifi網址:http://wifi.seoul.go.kr/index.do 韓國-其他Wifi 還有網上流傳的路由器預設密碼,有7-Eleven、麥當勞和Starbucks等等,因為是網上流傳,所以密碼未必準確,大家可以碰碰運氣! 1.KT SSID , KT_WLAN:1234567890, 123456789a, 1234567890c 2.Hellowireless:534f4b4354 3.Tbroadnet:123456789 4.KWI-B2200T, KWI-2200:SHOW3382 5.myLGnet, myLG070:123456789a, 987654321a, 1234567890, myLGNetfe07 6.SK, Tbroadnet:a123456789 7.세븐일레븐 (7-Eleven):2127393302 8.맥도날드 (McDonalds):16005252 9.스타벅스 (Starbucks):分店電話,收據上有,或者可以Starbucks網站查看。 台灣-iTaiwan WiFox_機場_WiFi11 更多 在台灣可用iTaiwan官方無線上網服務,於全台灣有5千多個WiFi熱點,地點包括旅遊景點、公共交通上都可使用,但要先申請才可使用。可以到台灣觀光局服務櫃枱臺或者台灣各地的旅遊服務中心當面申請,或者於當地連接iTaiwan上網,再於登入網頁作申請手續。大家去之前記得預先「做家功課」! 圖片來源:iTaiwan 更多 iTaiwan網址:https://itaiwan.gov.tw/index.php使用說明:http://itaiwan.gov.tw/FAQ_use.pdf

細嚐奈和美喫茶店葡日式下午茶!同場加映:世界之最金磚西多士
專題報導
Cheers!・2018-07-30

想與好閨蜜相約下午茶,但嫌傳統西式三層架太單調?不妨試試澳門大倉酒店奈和美的 「澳門和風菓子下午茶」。位於大倉酒店大堂的奈和美喫茶店,環境舒適優美,充滿日式風情,是與閨蜜悠閒相聚的好去處。 是次推介的下午茶更創意十足,除了一般常見的鹹甜點外,更加入了澳葡及日式元素,包括葡撻、木糠布甸、和菓子及鰻魚壽司,合共18種選擇,每款糕點均精緻誘人,配上精心製作的咖啡及茶,與閨蜜一同體驗難得悠哉的下午茶時光。 最令小編驚喜的,就是鹹點中的鰻魚壽司。 除了架上的食物外,下午茶套餐更包括這個日式小錦盒,內有馬卡龍及澳門必食手信杏仁餅。 同場加映:世界之最金磚西多士 除了下午茶外,奈和美喫茶店更提供日式輕食,當中一定不能錯過「大倉秘製吐司」(價錢:$88),吐司以東京大倉酒店秘傳 50 年的配方製作,工序繁複,麵包先以蛋汁浸泡一晚,再以慢火煎烘,外表金黃香脆,質感軟腍,配上牛油及楓糖漿食用,滿口都是蛋香味。吐司只會在每日限定時間(早上10時至中午12時)供應,而且數量只有10份,喜愛吃西多士的朋友萬萬不能錯過啊! 「大倉秘製吐司」是東京大倉酒店名物,連美國前總統布殊及奧巴馬也吃過。 澳門和菓子下午茶 地點:澳門大倉酒店奈和美 供應時間: 13:00-18:00 訂座電話:8883 5116 價格:$288++ (包括兩位咖啡或茶) 網址:https://www.hotelokuramacau.com/zh-hant/offer/dining/nagomi-hightea

【日本。名古屋】├住宿┤ The Royal Park Canvas Nagoya 皇家花園Canvas名古屋
走遍世界
80後愛旅行✈️・2025-08-17

ザ ロイヤルパーク キャンバス 名古屋 (The Royal Park Canvas Nagoya / 皇家花園Canvas名古屋) 是一間結合現代設計與名古屋地方特色的酒店,隸屬於Royal Park Hotels集團, 主打「CANVAS=交流、創造、享受」的概念,適合自由行旅客與商務人士入住。 從 JR 名古屋站(櫻通口),地下街「ユニモール(Unimall)」U10 出口,步行約 5 分鐘即可抵達,交通極為便利。 當天我們是一下飛機,從中部國際空港,乘μSKY列車到名鐵名古屋,再走地下街到「ユニモール(Unimall)」U10 出口 走地下街的好處是到處都是指示牌和地圖,拖著行李跟著走就行,比較方便。 The Royal Park Canvas Nagoya 提供以“樂趣”、“本地”和“聯繫”為主題的各種“邂逅”,建立人與人之間的聯繫。 來到酒店大堂,有3張大大的THE小桌子,是酒店的Logo圖案。 後面位置是酒店的餐廳"CASUAL DINING Monsieur ITOH",提供早、午餐。 大堂與公共空間大量採用再生材料與當地工藝品 可以自取一些日用品,例如梳子、浴帽、化妝包等。 大堂還有一個CANVAS LOUNGE,住宿客人可免費享用咖啡。 在酒店的3樓還有大浴場、自助洗衣房、自動售貨機。 住宿客人可以免費享用專用的大浴場。 住客專用的大浴場 (官網圖片) 這次入住的Package是 CANVAS STAY -ROOM ONLY,不包早、晚餐。 酒店擁有約153間客房,風格簡約清新,融合在地文化元素。 房間安排在9樓,每一層的走廊位置都有日本景點的相片做裝飾。 部分客房於2025年重新裝修,設計靈感取自名古屋城與和式工藝。 我們入住的就是重新裝修後的雙人大床房,房間約18.0㎡ 配有一張大床,寬 160 公分。 設有辦公/化妝桌,配有明亮的座台燈,蠻方便的! 提供2支免費的飲用水 洗手間是乾濕分離的,空間也不算小。 The Royal Park Canvas Nagoya 〒450-0002 名古屋市中村区名駅3-23-13 052-300-1111 https://www.royalparkhotels.co.jp/canvas/nagoya/ JR 名古屋站(櫻通口),地下街「ユニモール(Unimall)」U10 出口 2025年3月 雙人大床房(不含早餐) 16,960円 檢視較大的地圖

美食】風靡全球的美式漢堡品牌 FIVE GUYS,威尼斯人首間分店 3 月 2 日開幕!
生活在我城
Lifemagfoodie・2023-02-26

風靡全球嘅美式漢堡 Five Guys嚟到澳門威尼斯人喇~Five Guys 最大特色就係漢堡可以按自己喜好客制化,任揀自己鍾意嘅配料,配搭超過 25,000 款,嚟緊 3 月 2 日就開幕,首 50 位到店更有驚喜禮品!一齊嚟睇下有咩食先喇 ~ 薯條每日手工現切 食材每日新鮮現製,唔會隔夜,薯條都係每日手工現切,以純正花生油新鮮製作,非常有誠意!仲會定時檢查質量,確保炸出最完美熟度同口感!而呢個薯條真係做到外脆內軟,好有薯仔味,薯條可選原味或辣味。 雙層煙肉芝士漢堡 漢堡小編咗左雙層煙肉芝士漢堡,配料揀咗生菜、番茄、酸黃瓜、墨西哥辣椒、辣醬,漢堡扒肉汁飽滿,加上香脆嘅煙肉,口感豐富,辣勁十足! 芝士熱狗 熱狗部分點咗芝士熱狗,配料加入烤蘑菇、烤洋蔥、牛扒醬,牛扒醬熱狗都幾有特色,但唔夠漢堡出色。 奶昔 奶昔口味亦可以自由配搭,今次就揀咗煙肉同海鹽焦糖,味道出奇地夾,煙肉粒好有口感!而比較熱門嘅口味係士多啤梨Oreo 及Biscoff 餅乾味,非常值得一試! 無限汽水 Refill 汽水可以無限 Refill,保證食到你又飽又滿足! 5 Guys 嘅漢堡真係用心製作,而且客制化整到最符合自己口味,嚟緊 3 月 2 日就開幕啦!首 50 位到訪仲有驚喜禮物,快啲去試下喇! Five Guys 地址:澳門威尼斯人1樓 1033 & 1035 店(星巴克旁邊)

【馬交慳多拿Baby】自助餐4人同行,一人免費|參加Giveaway送零廢套裝 - 23.04.2021
生活在我城
Cheers!・2021-04-26

今集《馬交慳多拿Baby》為大家介紹澳門超值餐飲及購物優惠,部分低至6折。另外,我們每星期都有Giveaway遊戲,送禮物給大家,切勿錯過~ 1. CyberCTM 送你 Mofy Zero Waste Store 零廢套裝 Mofy Zero Waste Store 零廢套裝只要 $220 ,有齊純棉購物袋、環保餐具、手巾及牙刷,有效減少使用一次性即棄用品。去CyberCTM Facebook參加 giveaway 遊戲即有機會免費獲得一套,想加入零廢生活,快點參加吧~ Giveaway日期:4/21-4/27 Giveaway傳送門:https://bit.ly/2RYBnZi 2. 皇冠假日酒店 4月自助晚餐買四送一 皇冠假日酒店自助晚餐 4 月在做 4 人同行,1 人免費優惠!成人每位 $298加一 ,還可以任食生蠔海鮮! 來源:皇冠假日酒店 3. 金樹 大豐信用卡埋單低至 6 折優惠 金樹晚市料理用大豐信用卡埋單有低至 6 折優惠,想享受精緻日本特色菜又有好去處! 來源:大豐銀行 4. MPay用中銀增值 $500,即可享高達 $188 利是! MPay用中銀增值 $500,即可享高達 $188 利是!優惠期到 4 月 29 日,快點試試吧~ 來源:MPAY 5. 澳門國際銀行用戶去八佰伴購物送$100現金券 澳門國際銀行用戶福利,現在去八佰伴用銀行卡或信用卡簽滿 $1,000,即送 $100 新八佰伴現金券! 來源:澳門國際銀行

如何避免買廉航的手續費?
旅遊情報
MO560・2019-09-08

如何可以避免買廉航機票時的手續費? -------------------- 如果有買過廉航機票嘅fans都會知,大部分廉航都會加收手續費,澳門出發例如Tiger TW和Scoot。咁點樣做先可以避開個手續費? 首先,官網買就一定避免唔到,除非有Bug。所以,就要識得用OTA去Book機票。 以下圖為例:當時台灣虎航出的優惠,中秋快閃3日都有平機票,雙人來回就HKD$1556.4,另加HKD$260手續費。咁小編就去Trip.com比較一下,同一程機HKD$1516.03,無手續費。不容置疑,我馬上買了XD。 緊貼最新最潮澳門信用卡、飛行里數、旅遊資訊,記得讚好MO560的Facebook! Trip.com的話,除左廉航(LCC)之外,如果要買中國大陸的航空公司都值得睇一睇比較一下價錢,因為始終Ctrip同佢地關係密切,(Trip.com=Ctrip擕程國際版),有時候個價錢可以差好遠,小編見過最多的是足足相差了HKD$10000。。。 小編常用的買機票OTA有Trip.com和cheaptickets,一個是Ctrip擕程旗下,另一個就是Expedia旗下。 小編一直以來在OTA買過數十次機票,從未試過有問題。如果真的擔心的話其實都可以Book完機票之後直接同航空公司確認一下。 即刻預訂 - Trip.com:https://hk.trip.com即刻預訂 - Cheaptickets:https://www.cheaptickets.hk Trip.com 詳細教學:https://motravel.info/how-to-play-trip-comTrip.com 最新優惠碼:https://motravel.info/trip-com-promotion 緊貼最新最潮澳門信用卡、飛行里數、旅遊資訊,記得讚好MO560的Facebook! -------------------- 喜歡小編的文章嗎?比個Like支持我啦 :) Facebook: MO560Instagram: mo560_travelWebsite: https://motravel.info 想追蹤最新最潮澳門信用卡、飛行里數、旅遊資訊?將MO560設定為 「搶先看/See First」!