端開發(fā):01-移動(dòng)開發(fā)體檢預(yù)約)
第6章 移動(dòng)端開發(fā)-體檢預(yù)約1. 移動(dòng)端開發(fā)1.1 移動(dòng)端開發(fā)方式隨著移動(dòng)互聯(lián)網(wǎng)的興起和手機(jī)的普及目前移動(dòng)端應(yīng)用變得愈發(fā)重要成為了各個(gè)商家的必爭(zhēng)之地。例如我們可以使用手機(jī)購(gòu)物、支付、打車、玩游戲、訂酒店、購(gòu)票等以前只能通過PC端完成的事情現(xiàn)在通過手機(jī)都能夠?qū)崿F(xiàn)而且更加方便而這些都需要移動(dòng)端開發(fā)進(jìn)行支持那如何進(jìn)行移動(dòng)端開發(fā)呢移動(dòng)端開發(fā)主要有三種方式1、基于手機(jī)API開發(fā)原生APP2、基于手機(jī)瀏覽器開發(fā)移動(dòng)web3、混合開發(fā)混合APP1.1.1 基于手機(jī)API開發(fā)手機(jī)端使用手機(jī)API例如使用Android、ios 等進(jìn)行開發(fā)服務(wù)端只是一個(gè)數(shù)據(jù)提供者。手機(jī)端請(qǐng)求服務(wù)端獲取數(shù)據(jù)json、xml格式并在界面進(jìn)行展示。這種方式相當(dāng)于傳統(tǒng)開發(fā)中的C/S模式即需要在手機(jī)上安裝一個(gè)客戶端軟件。這種方式需要針對(duì)不同的手機(jī)系統(tǒng)分別進(jìn)行開發(fā)目前主要有以下幾個(gè)平臺(tái)1、蘋果ios系統(tǒng)版本開發(fā)語(yǔ)言是Objective-C2、安卓Android系統(tǒng)版本開發(fā)語(yǔ)言是Java3、微軟Windows phone系統(tǒng)版本開發(fā)語(yǔ)言是C#4、塞班symbian系統(tǒng)版本開發(fā)語(yǔ)言是C此種開發(fā)方式舉例手機(jī)淘寶、抖音、今日頭條、大眾點(diǎn)評(píng)1.1.2 基于手機(jī)瀏覽器開發(fā)生存在瀏覽器中的應(yīng)用基本上可以說是觸屏版的網(wǎng)頁(yè)應(yīng)用。這種開發(fā)方式相當(dāng)于傳統(tǒng)開發(fā)中的B/S模式也就是手機(jī)上不需要額外安裝軟件直接基于手機(jī)上的瀏覽器進(jìn)行訪問。這就需要我們編寫的html頁(yè)面需要根據(jù)不同手機(jī)的尺寸進(jìn)行自適應(yīng)調(diào)節(jié)目前比較流行的是html5開發(fā)。除了直接通過手機(jī)瀏覽器訪問還可以將頁(yè)面內(nèi)嵌到一些應(yīng)用程序中例如通過微信公眾號(hào)訪問html5頁(yè)面。這種開發(fā)方式不需要針對(duì)不同的手機(jī)系統(tǒng)分別進(jìn)行開發(fā)只需要開發(fā)一個(gè)版本就可以在不同的手機(jī)上正常訪問。本項(xiàng)目會(huì)通過將我們開發(fā)的html5頁(yè)面內(nèi)嵌到微信公眾號(hào)這種方式進(jìn)行開發(fā)。1.1.3 混合開發(fā)是半原生半Web的混合類App。需要下載安裝看上去類似原生App訪問的內(nèi)容是Web網(wǎng)頁(yè)。其實(shí)就是把HTML5頁(yè)面嵌入到一個(gè)原生容器里面。1.2 微信公眾號(hào)開發(fā)要進(jìn)行微信公眾號(hào)開發(fā)首先需要訪問微信公眾平臺(tái)官網(wǎng)https://mp.weixin.qq.com/1.2.1 帳號(hào)分類在微信公眾平臺(tái)可以看到有四種帳號(hào)類型服務(wù)號(hào)、訂閱號(hào)、小程序、企業(yè)微信原企業(yè)號(hào)。本項(xiàng)目會(huì)選擇訂閱號(hào)這種方式進(jìn)行公眾號(hào)開發(fā)。1.2.2 注冊(cè)帳號(hào)要開發(fā)微信公眾號(hào)首先需要注冊(cè)成為會(huì)員然后就可以登錄微信公眾平臺(tái)進(jìn)行自定義菜單的設(shè)置。注冊(cè)頁(yè)面https://mp.weixin.qq.com/cgi-bin/registermidpage?actionindexlangzh_CNtoken選擇訂閱號(hào)進(jìn)行注冊(cè)輸入郵箱、郵箱驗(yàn)證碼、密碼、確認(rèn)密碼等按照頁(yè)面流程進(jìn)行注冊(cè)1.2.3 自定義菜單注冊(cè)成功后就可以使用注冊(cè)的郵箱和設(shè)置的密碼進(jìn)行登錄登錄成功后點(diǎn)擊左側(cè)“自定義菜單”進(jìn)入自定義菜單頁(yè)面在自定義菜單頁(yè)面可以根據(jù)需求創(chuàng)建一級(jí)菜單和二級(jí)菜單其中一級(jí)菜單最多可以創(chuàng)建3個(gè)每個(gè)一級(jí)菜單下面最多可以創(chuàng)建5個(gè)二級(jí)菜單。每個(gè)菜單由菜單名稱和菜單內(nèi)容組成其中菜單內(nèi)容有3中形式發(fā)送消息、跳轉(zhuǎn)網(wǎng)頁(yè)、跳轉(zhuǎn)小程序。1.2.4 上線要求如果是個(gè)人用戶身份注冊(cè)的訂閱號(hào)則自定義菜單的菜單內(nèi)容不能進(jìn)行跳轉(zhuǎn)網(wǎng)頁(yè)因?yàn)閭€(gè)人用戶目前不支持微信認(rèn)證而跳轉(zhuǎn)網(wǎng)頁(yè)需要微信認(rèn)證之后才有權(quán)限。如果是企業(yè)用戶首先需要進(jìn)行微信認(rèn)證通過后就可以進(jìn)行跳轉(zhuǎn)網(wǎng)頁(yè)了跳轉(zhuǎn)網(wǎng)頁(yè)的地址要求必須有域名并且域名需要備案通過。2. 需求分析和環(huán)境搭建2.1 需求分析用戶在體檢之前需要進(jìn)行預(yù)約可以通過電話方式進(jìn)行預(yù)約此時(shí)會(huì)由體檢中心客服人員通過后臺(tái)系統(tǒng)錄入預(yù)約信息。用戶也可以通過手機(jī)端自助預(yù)約。本章節(jié)開發(fā)的功能為用戶通過手機(jī)自助預(yù)約。預(yù)約流程如下1、訪問移動(dòng)端首頁(yè)2、點(diǎn)擊體檢預(yù)約進(jìn)入體檢套餐列表頁(yè)面3、在體檢套餐列表頁(yè)面點(diǎn)擊具體套餐進(jìn)入套餐詳情頁(yè)面4、在套餐詳情頁(yè)面點(diǎn)擊立即預(yù)約進(jìn)入預(yù)約頁(yè)面5、在預(yù)約頁(yè)面錄入體檢人相關(guān)信息點(diǎn)擊提交預(yù)約效果如下圖2.2 搭建移動(dòng)端工程本項(xiàng)目是基于SOA架構(gòu)進(jìn)行開發(fā)前面我們已經(jīng)完成了后臺(tái)系統(tǒng)的部分功能開發(fā)在后臺(tái)系統(tǒng)中都是通過Dubbo調(diào)用服務(wù)層發(fā)布的服務(wù)進(jìn)行相關(guān)的操作。本章節(jié)我們開發(fā)移動(dòng)端工程也是同樣的模式所以我們也需要在移動(dòng)端工程中通過Dubbo調(diào)用服務(wù)層發(fā)布的服務(wù)如下圖2.2.1 導(dǎo)入maven坐標(biāo)在health_common工程的pom.xml文件中導(dǎo)入阿里短信發(fā)送的maven坐標(biāo)!--阿里云短信-- dependency groupIdcom.aliyun/groupId artifactIdaliyun-java-sdk-core/artifactId version3.3.1/version /dependency dependency groupIdcom.aliyun/groupId artifactIdaliyun-java-sdk-dysmsapi/artifactId version1.0.0/version /dependency !--騰訊云短信-- dependency groupIdcom.tencentcloudapi/groupId artifactIdtencentcloud-sdk-java/artifactId version3.1.62/version!-- 注這里只是示例版本號(hào)請(qǐng)獲取并替換為 最新的版本號(hào) -- /dependency2.2.2 導(dǎo)入通用組件在health_common工程中導(dǎo)入如下通用組件ValidateCodeUtils工具類package com.liusp.utils; import java.util.Random; /** * 隨機(jī)生成驗(yàn)證碼工具類 */ public class ValidateCodeUtils { /** * 隨機(jī)生成驗(yàn)證碼 * param length 長(zhǎng)度為4位或者6位 * return */ public static Integer generateValidateCode(int length){ Integer code null; if(length 4){ code new Random().nextInt(9999);//生成隨機(jī)數(shù)最大為9999 if(code 1000){ code code 1000;//保證隨機(jī)數(shù)為4位數(shù)字 } }else if(length 6){ code new Random().nextInt(999999);//生成隨機(jī)數(shù)最大為999999 if(code 100000){ code code 100000;//保證隨機(jī)數(shù)為6位數(shù)字 } }else{ throw new RuntimeException(只能生成4位或6位數(shù)字驗(yàn)證碼); } return code; } /** * 隨機(jī)生成指定長(zhǎng)度字符串驗(yàn)證碼 * param length 長(zhǎng)度 * return */ public static String generateValidateCode4String(int length){ Random rdm new Random(); String hash1 Integer.toHexString(rdm.nextInt()); String capstr hash1.substring(0, length); return capstr; } }RedisMessageConstant常量類package com.liusp.constant; public class RedisMessageConstant { public static final String SENDTYPE_ORDER 001;//用于緩存體檢預(yù)約時(shí)發(fā)送的驗(yàn)證碼 public static final String SENDTYPE_LOGIN 002;//用于緩存手機(jī)號(hào)快速登錄時(shí)發(fā)送的驗(yàn)證碼 public static final String SENDTYPE_GETPWD 003;//用于緩存找回密碼時(shí)發(fā)送的驗(yàn)證碼 }2.2.3 health_mobile創(chuàng)建移動(dòng)端工程health_mobile打包方式為war用于存放Controller在Controller中通過Dubbo可以遠(yuǎn)程訪問服務(wù)層相關(guān)服務(wù)所以需要依賴health_interface接口工程。pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd parent groupIdcom.liusp/groupId artifactIdhealth_parent/artifactId version0.0.1-SNAPSHOT/version !--relativePath/ lt;!ndash; lookup parent from repository ndash;gt;-- /parent modelVersion4.0.0/modelVersion groupIdcom.liusp/groupId artifactIdhealth_mobile/artifactId version0.0.1-SNAPSHOT/version packagingwar/packaging namehealth_mobile/name descriptionDemo project for Spring Boot/description properties java.version1.8/java.version project.build.sourceEncodingUTF-8/project.build.sourceEncoding project.reporting.outputEncodingUTF-8/project.reporting.outputEncoding spring-boot.version2.3.7.RELEASE/spring-boot.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope exclusions exclusion groupIdorg.junit.vintage/groupId artifactIdjunit-vintage-engine/artifactId /exclusion /exclusions /dependency !--Dubbo集成SpringBoot依賴-- dependency groupIdcom.alibaba.spring.boot/groupId artifactIddubbo-spring-boot-starter/artifactId version2.0.0/version /dependency !--zkclient zookeeper客戶端-- dependency groupIdcom.101tec/groupId artifactIdzkclient/artifactId version0.10/version /dependency dependency groupIdcom.liusp/groupId artifactIdhealth_interface/artifactId version0.0.1-SNAPSHOT/version /dependency !-- 引入redis-- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !--spring security-- !--dependency-- !--groupIdorg.springframework.boot/groupId-- !--artifactIdspring-boot-starter-security/artifactId-- !--/dependency-- !--文件上傳-- dependency groupIdcommons-fileupload/groupId artifactIdcommons-fileupload/artifactId version1.3.3/version /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.72/version /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version${spring-boot.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.8.1/version configuration source1.8/source target1.8/target encodingUTF-8/encoding /configuration /plugin plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId version2.3.7.RELEASE/version configuration mainClasscom.liusp.HealthMobileApplication/mainClass /configuration executions execution idrepackage/id goals goalrepackage/goal /goals /execution /executions /plugin /plugins /build /project靜態(tài)資源CSS、html、img等詳見資料application.yml 配置文件server: port: 80 #dubbo dubbo: #設(shè)置服務(wù)器應(yīng)用名稱 application: name: health_mobile #指定注冊(cè)中心 registry: address: zookeeper://127.0.0.1:2181 #超時(shí)全局設(shè)置 10分鐘 #checkfalse 不檢查服務(wù)提供方開發(fā)階段建議設(shè)置為false #checktrue 啟動(dòng)時(shí)檢查服務(wù)提供方如果服務(wù)提供方?jīng)]有啟動(dòng)則報(bào)錯(cuò) consumer: timeout: 600000 check: false spring: redis: host: 127.0.0.1 #文件上傳 servlet: multipart: enabled: true max-file-size: 10MB max-request-size: 100MB #設(shè)置日期格式 jackson: date-format: yyyy-MM-dd time-zone: GMT8SpringBoot啟動(dòng)類添加Dubbo支持注解//添加對(duì)Dubbo的支持 EnableDubbo3. 套餐列表頁(yè)面動(dòng)態(tài)展示移動(dòng)端首頁(yè)為/pages/index.html效果如下點(diǎn)擊體檢預(yù)約直接跳轉(zhuǎn)到體檢套餐列表頁(yè)面/pages/setmeal.html3.1 完善頁(yè)面3.1.1 展示套餐信息ul classlist li classlist-item v-forsetmeal in setmealList a classlink-page :hrefsetmeal_detail.html?idsetmeal.id img classimg-object f-left :srchttp://pqjroc654.bkt.clouddn.com/setmeal.img alt div classitem-body h4 classellipsis item-title{{setmeal.name}}/h4 p classellipsis-more item-desc{{setmeal.remark}}/p p classitem-keywords span{{setmeal.sex 0 ? 性別不限 : setmeal.sex 1 ? 男:女}}/span span{{setmeal.age}}/span /p /div /a /li /ul3.1.2 獲取套餐列表數(shù)據(jù)var vue new Vue({ el:#app, data:{ setmealList:[] }, mounted (){ //發(fā)送ajax請(qǐng)求獲取所有的套餐數(shù)據(jù)賦值給setmealList模型數(shù)據(jù)用于頁(yè)面展示 axios.get(/setmeal/getAllSetmeal.do).then((res) { if(res.data.flag){ //查詢成功給模型數(shù)據(jù)賦值 this.setmealList res.data.data; }else{ //查詢失敗彈出提示信息 this.$message.error(res.data.message); } }); } });3.2 后臺(tái)代碼3.2.1 Controller在health_mobile工程中創(chuàng)建SetmealController并提供getSetmeal方法在此方法中通過Dubbo遠(yuǎn)程調(diào)用套餐服務(wù)獲取套餐列表數(shù)據(jù)package com.liusp.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.liusp.constant.MessageConstant; import com.liusp.entity.Result; import com.liusp.pojo.Setmeal; import com.liusp.service.SetmealService; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/setmeal) public class SetmealController { Reference//(check false) private SetmealService setmealService; //獲取所有套餐信息 RequestMapping(/getAllSetmeal.do) public Result getAllSetmeal(){ try{ ListSetmeal list setmealService.findAll(); return new Result(true, MessageConstant.GET_SETMEAL_LIST_SUCCESS,list); }catch (Exception e){ e.printStackTrace(); return new Result(false,MessageConstant.GET_SETMEAL_LIST_FAIL); } } }3.2.2 服務(wù)接口在SetmealService服務(wù)接口中擴(kuò)展findAll方法public ListSetmeal findAll();3.2.3 服務(wù)實(shí)現(xiàn)類在SetmealServiceImpl服務(wù)實(shí)現(xiàn)類中實(shí)現(xiàn)findAll方法public ListSetmeal findAll() { return setmealDao.findAll(); }1233.2.4 Dao接口在SetmealDao接口中擴(kuò)展findAll方法public ListSetmeal findAll();3.2.5 Mapper映射文件在SetmealDao.xml映射文件中擴(kuò)展SQL語(yǔ)句select idfindAll resultTypecom.liusp.pojo.Setmeal select * from t_setmeal /select4. 套餐詳情頁(yè)面動(dòng)態(tài)展示前面我們已經(jīng)完成了體檢套餐列表頁(yè)面動(dòng)態(tài)展示點(diǎn)擊其中任意一個(gè)套餐則跳轉(zhuǎn)到對(duì)應(yīng)的套餐詳情頁(yè)面/pages/setmeal_detail.html并且會(huì)攜帶此套餐的id作為參數(shù)提交。請(qǐng)求路徑格式http://localhost/pages/setmeal_detail.html?id10在套餐詳情頁(yè)面需要展示當(dāng)前套餐的信息包括圖片、套餐名稱、套餐介紹、適用性別、適用年齡、此套餐包含的檢查組信息、檢查組包含的檢查項(xiàng)信息等。4.1 完善頁(yè)面4.1.1 獲取請(qǐng)求參數(shù)中套餐id在頁(yè)面中已經(jīng)引入了healthmobile.js文件此文件中已經(jīng)封裝了getUrlParam方法可以根據(jù)URL請(qǐng)求路徑中的參數(shù)名獲取對(duì)應(yīng)的值function getUrlParam(paraName) { var url document.location.toString(); //alert(url); var arrObj url.split(?); if (arrObj.length 1) { var arrPara arrObj[1].split(); var arr; for (var i 0; i arrPara.length; i) { arr arrPara[i].split(); if (arr ! null arr[0] paraName) { return arr[1]; } } return ; } else { return ; } }在setmeal_detail.html中調(diào)用上面定義的方法獲取套餐id的值script var id getUrlParam(id); /script4.1.2 獲取套餐詳細(xì)信息script var vue new Vue({ el:#app, data:{ imgUrl:null,//套餐對(duì)應(yīng)的圖片鏈接 setmeal:{} }, mounted(){ axios.post(/setmeal/findById.do?id id).then((response) { if(response.data.flag){ this.setmeal response.data.data; this.imgUrl http://pqjroc654.bkt.clouddn.com/ this.setmeal.img; } }); } }); /script4.1.3 展示套餐信息div classcontentBox div classcard div classproject-img img :srcimgUrl width100% height100% / /div div classproject-text h4 classtit{{setmeal.name}}/h4 p classsubtit{{setmeal.remark}}/p p classkeywords span{{setmeal.sex 0 ? 性別不限 : setmeal.sex 1 ? 男:女}}/span span{{setmeal.age}}/span /p /div /div div classtable-listbox div classbox-title i classicon-zhenspan classpath1/spanspan classpath2/span/i span套餐詳情/span /div div classbox-table div classtable-title div classtit-item flex2項(xiàng)目名稱/div div classtit-item flex3項(xiàng)目?jī)?nèi)容/div div classtit-item flex3項(xiàng)目解讀/div /div div classtable-content ul classtable-list li classtable-item v-forcheckgroup in setmeal.checkGroups div classitem flex2{{checkgroup.name}}/div div classitem flex3 label v-forcheckitem in checkgroup.checkItems {{checkitem.name}} /label /div div classitem flex3{{checkgroup.remark}}/div /li /ul /div div classbox-button a clicktoOrderInfo() classorder-btn立即預(yù)約/a /div /div /div /div4.2 后臺(tái)代碼4.2.1 Controller在SetmealController中提供findById方法//根據(jù)id查詢套餐信息 RequestMapping(/findById.do) public Result findById(int id){ try{ Setmeal setmeal setmealService.findById(id); return new Result(true,MessageConstant.QUERY_SETMEAL_SUCCESS,setmeal); }catch (Exception e){ e.printStackTrace(); return new Result(false,MessageConstant.QUERY_SETMEAL_FAIL); } }4.2.2 服務(wù)接口在SetmealService服務(wù)接口中提供findById方法public Setmeal findById(int id);4.2.3 服務(wù)實(shí)現(xiàn)類在SetmealServiceImpl服務(wù)實(shí)現(xiàn)類中實(shí)現(xiàn)findById方法public Setmeal findById(int id) { return setmealDao.findById(id); }4.2.4 Dao接口在SetmealDao接口中提供findById方法public Setmeal findById(int id);在CheckGroupDao接口中提供findCheckGroupById方法ListCheckGroup findCheckGroupById(Integer id);在CheckItemDao接口中提供findCheckItemById方法public ListCheckItem findCheckItemById(Integer id);4.2.5 Mapper映射文件此處會(huì)使用mybatis提供的關(guān)聯(lián)查詢?cè)诟鶕?jù)id查詢套餐時(shí)同時(shí)將此套餐包含的檢查組都查詢出來(lái)并且將檢查組包含的檢查項(xiàng)都查詢出來(lái)。SetmealDao.xml文件resultMap typecom.liusp.pojo.Setmeal idbaseResultMap id columnid propertyid/ result columnname propertyname/ result columncode propertycode/ result columnhelpCode propertyhelpCode/ result columnsex propertysex/ result columnage propertyage/ result columnprice propertyprice/ result columnremark propertyremark/ result columnattention propertyattention/ result columnimg propertyimg/ /resultMap resultMap typecom.liusp.pojo.Setmeal idfindByIdResultMap extendsbaseResultMap collection propertycheckGroups javaTypeArrayList ofTypecom.liusp.pojo.CheckGroup columnid selectcom.liusp.dao.CheckGroupDao.findCheckGroupById /collection /resultMap select idfindById resultMapfindByIdResultMap select * from t_setmeal where id#{id} /selectCheckGroupDao.xml文件resultMap typecom.liusp.pojo.CheckGroup idbaseResultMap id columnid propertyid/ result columnname propertyname/ result columncode propertycode/ result columnhelpCode propertyhelpCode/ result columnsex propertysex/ result columnremark propertyremark/ result columnattention propertyattention/ /resultMap resultMap typecom.liusp.pojo.CheckGroup idfindByIdResultMap extendsbaseResultMap collection propertycheckItems javaTypeArrayList ofTypecom.liusp.pojo.CheckItem columnid selectcom.liusp.dao.CheckItemDao.findCheckItemById /collection /resultMap !--根據(jù)套餐id查詢檢查項(xiàng)信息-- select idfindCheckGroupById resultMapfindByIdResultMap select * from t_checkgroup where id in (select checkgroup_id from t_setmeal_checkgroup where setmeal_id#{id}) /selectCheckItemDao.xml文件!--根據(jù)檢查組id查詢檢查項(xiàng)信息-- select idfindCheckItemById resultTypecom.liusp.pojo.CheckItem select * from t_checkitem where id in (select checkitem_id from t_checkgroup_checkitem where checkgroup_id#{id}) /select