springboot整合web開發的各個組件在前面已經有詳細的介紹,下面是用springboot整合layui實現了基本的增刪改查。
同時在學習mui開發app,也就用mui實現了一個簡單的自動登錄和用戶列表上拉刷新的app。
下面是自己實現前的思路:
1. web端實現用戶的增刪改查,SSM實現。 Spring + SpringMVC +Mybatis + PageHelper
表主要有兩個user表和token表。
user表就是基本的信息(ID、username、password、userfullname、createtime等字段)
token表id、token創建時間、token串、失效時間、username、userIdcode等信息
2. 安卓端實現token自動登錄和下拉刷新查詢列表。
1. 自動登錄的思路:
(1) 首次登錄: 帶着username、password去后台請求登錄信息,
后台如果登錄成功返回一個{ token,username,userfullname} , token表包括:id、token創建時間、token串、失效時間、username、userIdcode等信息。token的生成方式可以用md5算法加密username生成
(2) 主頁面訪問的時候每次先帶着本地的token去后台驗證一下token,如果token失效的話就返回到登錄界面,如果token有效期在5天以內就在后台生成一個token並且重新刷新本地token
后台返回json數據,msg包括:invalidToken--表示無效token,需要退出到登陸界面;
ok:表示有效,然后根據返回的token串與本地串是否一致,如果不一致更新本地token即可。
(3)安卓端退出的時候清除本地的token
2.安卓端分頁下拉刷新展示所有的user信息
===========Web端實現用戶增刪改查========
主要是springboot+layui+thymeleaf。
主要是layui整合thymeleaf的時候,html的所有的自閉標簽都必須帶結束標記,比如<input > 必須寫為<input />;而且標簽的屬性必須有值,例如<input checked /> 必須是<input checked="checked"/>,否則會報解析錯誤。而且頁面的JS有時在解析時也報語法錯誤,所以JS盡量寫在單獨的JS文件中。
另外需要注意的是使用springboot+thymeleaf的時候,如果需要訪問html頁面,所有的請求都必須經過后台進行轉發。即使是在layui的框架中使用iframe等進行引進html也需要到后台進行轉發一下。
目錄結構如下:
下面貼出pom.xml和application.properties
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.qlq</groupId> <artifactId>springboot-ssm</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 設置Tomcat打包的時候不打包下面配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> <!--springboot單元測試 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--熱加載 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <!-- spring-boot整合mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <!-- mysql驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <!-- 使用事務需要引入這個包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- 引入 spring aop 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--pagehelper插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.2</version> </dependency> <!-- 引入 freemarker 模板依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <!-- 引入 thymeleaf 模板依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- commons工具包 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2</version> </dependency> <!-- 阿里的fastjson用於手動轉JSON --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.56</version> </dependency> </dependencies> <distributionManagement> <repository> <id>releases</id> <url>http://192.168.0.133:8081/repository/maven-releases/</url> </repository> <snapshotRepository> <id>snapshots</id> <url>http://192.168.0.133:8081/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement> <build> <!-- 配置了很多插件 --> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
############################################################ # # 日志相關配置(默認集成的有slf4j,Logback等) # ############################################################ #指定配置文件的位置,只能是xml或者groovy結尾 #logging.config=classpath:logback.xml #默認的日志級別 logging.level.root=INFO # mapper 接口所在的包設置為 debug logging.level.cn.qlq.mapper=DEBUG #生成日志文件的位置 logging.file=G:/springboot.log #生成日志文件的目錄,名稱默認為spring.log #logging.path=e:/ #指定日志的格式 #logging.pattern.console=%d{yyyy/MM/dd-HH:mm:ss} [%thread] %-5level %clr(%logger){cyan} %clr(%msg%n){green} #logging.pattern.file=%d{yyyy/MM/dd-HH:mm} [%thread] %-5level %logger- %msg%n ############################################################ # # Mybatis settings # ############################################################ #jiazai mybatis peizhiwenjian(**代表任意目錄,*代表任意多個字符) mybatis.mapper-locations = classpath:mapper/**/*Mapper.xml mybatis.config-location = classpath:mybatis/SqlMapConfig.xml mybatis.type-aliases-package = cn.qlq.bean ############################################################ # # datasource settings # ############################################################ spring.datasource.driver-class-name= com.mysql.jdbc.Driver spring.datasource.url = jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8 spring.datasource.username = root spring.datasource.password = 123456 ############################################################ # # Server 服務端相關配置 # ############################################################ # 配置api端口號 server.port=8088 # 配置context-path, 一般來說這個配置在正式發布的時候不配置 #server.context-path=/MySpringboot server.serverlet-path=*.do,*.action # 錯誤頁,指定發生錯誤時,跳轉的URL --> BasicErrorController #server.error.path=/error # session最大超時時間(分鍾),默認為30分鍾 server.session-timeout=60 # 該服務綁定IP地址,啟動服務器時如本機不是該IP地址則拋出異常啟動失敗, # 只有特殊需求的情況下才配置, 具體根據各自的業務來設置 #server.address=192.168.1.2 ############################################################ # Server - tomcat 相關常用配置 ############################################################ # tomcat最大線程數, 默認為200 #server.tomcat.max-threads=250 # tomcat的URI編碼 server.tomcat.uri-encoding=UTF-8 # 存放Tomcat的日志、Dump等文件的臨時文件夾,默認為系統的tmp文件夾 #(如:C:%users\Shanhy\AppData\Local\Temp) #server.tomcat.basedir=H:/springboot-tomcat-tmp # 打開Tomcat的Access日志,並可以設置日志格式的方法: #server.tomcat.access-log-enabled=true #server.tomcat.access-log-pattern= # accesslog目錄,默認在basedir/logs #server.tomcat.accesslog.directory= # 日志文件目錄 #logging.path=H:/springboot-tomcat-tmp # 日志文件名稱,默認為spring.log #logging.file=myapp.log ############################################################ # # freemarker 靜態資源配置 # ############################################################ #設定ftl文件路徑 spring.freemarker.template-loader-path=classpath:/templates # 關閉緩存, 即時刷新, 上線生產環境需要改為true spring.freemarker.cache=false #spring.freemarker.enabled=false spring.freemarker.charset=UTF-8 spring.freemarker.check-template-location=false spring.freemarker.content-type=text/html spring.freemarker.expose-request-attributes=true spring.freemarker.expose-session-attributes=true spring.freemarker.request-context-attribute=request spring.freemarker.suffix=.ftl ############################################################ # # thymeleaf 靜態資源配置 # ############################################################ spring.thymeleaf.prefix=classpath:/templates/thymeleaf/x-admin/ spring.thymeleaf.suffix=.html spring.thymeleaf.mode=HTML5 spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.content-type=text/html # 關閉緩存, 即時刷新, 上線生產環境需要改為true spring.thymeleaf.cache=false #關閉thymeleaf引擎 #spring.thymeleaf.enabled=false ############################################################ # # JSP 配置 # ############################################################ #spring.mvc.view.suffix=.jsp #spring.mvc.view.prefix=/WEB-INF/jsp/ #設定靜態文件路徑,js,css等 spring.mvc.static-path-pattern=/static/** #靜態資源的存儲位置 #spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ ############################################################ # # 配置i18n 資源文件,供thymeleaf 讀取 # ############################################################ spring.messages.basename=i18n/messages spring.messages.cache-seconds=3600 spring.messages.encoding=UTF-8 ############################################################ # # 格式化日期類型為JSON的格式 # ############################################################ spring.jackson.date-format=yyyy-MM-dd spring.jackson.time-zone=GMT+8 spring.jackson.serialization.write-dates-as-timestamps=false
git地址:https://github.com/qiao-zhi/springboot-ssm.git
===========app端實現自動登錄和用戶列表查詢========
app的開發主要是采用HBuilder建的移動app工程,以及mui框架。mui可以調用手機自帶的API,比如手機二維碼掃描、微信登錄等。而且HBuilder已經集成了mui,所以新建工程的時候選擇的是帶登錄模板的工程。而且只有apk打包的程序才可以訪問h5+。直接在瀏覽器訪問是不可行的。
下面是自己開發過程中對mui注意事項的總結:
0.每一個頁面對應一個JS。而且在JS第一行首先調用mui.init();進行初始化。 1.mui的localStorage 可用於本地存儲。除非用JS刪除 localStorage.setItem('$settings', JSON.stringify(settings)); var stateText = localStorage.getItem('$state') || "{}"; 2.不要在沒有plus和mui的環境下調用相關API 普通瀏覽器里沒有plus環境,只有HBuilder真機運行和打包后才能運行plus api。 在普通瀏覽器里運行時plus api時控制台必然會輸出plus is not defined錯誤提示。 mui作為一個前端框架,你必須保證當前頁面引入了mui.js。否則也會出現mui is not defined。 3.不要在plus和mui未完成初始化時調用相關API 就像在dom初始化完成前(DOMContentLoaded)去操作dom,就會報錯是一樣的道理。 plus和mui都需要初始化,在初始化完成后調用再調用。 一般我們在plusready的回調事件里調用plus api。=======也就是說一般我們都要先mui.init({...}),然后mui相關所有的JS函數都寫在mui.plusReady里面,遵循大部分前端框架的規則 mui框架對此進行了封裝,寫法更簡單: mui.init({ pullRefresh: { container: '#pullrefresh', down: { style:'circle', callback: pulldownRefresh }, up: { auto:true, contentrefresh: '正在加載...', callback: pullupRefresh } } }); mui.plusReady(function() { mui.toast("提示"); }); 4.mui頁面跳轉的方式:===預加載的方式 (1)在$.plusReady中提前加載頁面 var mainPage = $.preload({ "id": 'main', "url": 'main.html' }); (2)通過函數跳轉頁面的方法: var toMain = function() { $.fire(mainPage, 'show', null); setTimeout(function() { $.openWindow({ id: 'main', show: { aniShow: 'pop-in' }, waiting: { autoShow: false } }); }, 0); }; 通過mui.fire()方法可觸發目標窗口的自定義事件。 5.mui頁面跳轉的方式:===預加載的方式,也可以通過a標簽的方式進行加載 一般在新的頁面用下面格式: <body> <!--上面的導航窗格,mui-pull-left表示居左還是居右--> <header class="mui-bar mui-bar-nav"> <!--返回按鈕--> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">三行列表</h1> </header> <!--下面存放所有的內容--> <div class="mui-content"> </div> <body> 6.mui重寫手機返回按鈕的事件: <script> mui.init({ swipeBack:true //啟用右滑關閉功能 }); mui.plusReady(function() { mui.back = function(event) { alert("xxx"); }; }); </script> 一般在主頁面重寫返回按鈕的事件:--雙擊退出應用 //-- $.oldBack = mui.back; var backButtonPress = 0; mui.back = function(event) { backButtonPress++; if (backButtonPress > 1) { plus.runtime.quit(); } else { plus.nativeUI.toast('再按一次退出應用'); } setTimeout(function() { backButtonPress = 0; }, 1000); return false; }; 7. mui帶箭頭的列表導航的格式 <ul id="list" class="mui-table-view mui-table-view-chevron"> <li class="mui-table-view-cell"> <a class="mui-navigate-right" href="view/user.html"> 進入用戶列表 </a> </li> <li class="mui-table-view-cell mui-collapse"> <a class="mui-navigate-right" href="#"> 測試連接 </a> <ul class="mui-table-view mui-table-view-chevron"> <li class="mui-table-view-cell"> <a class="mui-navigate-right" href="examples/actionsheet.html"> 頁面1 </a> </li> <li class="mui-table-view-cell mui-plus-visible"> <a class="mui-navigate-right" href="examples/actionsheet-plus.html"> 頁面2 </a> </li> </ul> </li> </ul>
1.自動登錄的主要思想:
如果對模板的項目稍有研究可以發現,app默認的頁面是login.html,如果登錄成功或者是自動登錄而且自動登錄的信息在本地存儲的是正確的就調用toMain()方法跳轉到main.html。
自動登錄的思想就是:登錄成功之后將從后台生成一個token串保存到本地,每次打開app的時候如果是自動登錄就帶着本地的token去后台驗證,當然后台的代碼在上面的springboot項目地址都實現了。這里主要介紹前端的交互。
1.重寫app.js的登錄方法:(我們知道mui的ajax請求本身是跨域的)
/** * 用戶登錄 **/ owner.login = function(loginInfo, callback) { callback = callback || $.noop; loginInfo = loginInfo || {}; loginInfo.account = loginInfo.account || ''; loginInfo.password = loginInfo.password || ''; if (loginInfo.account.length < 5) { return callback('賬號最短為 5 個字符'); } if (loginInfo.password.length < 5) { return callback('密碼最短為 5 個字符'); } var users = JSON.parse(localStorage.getItem('$users') || '[]'); //模擬登陸 var loginAddress = appServerAddressPrefix + "/mobile/doLogin.html"; $.post(loginAddress, { "username":loginInfo.account, "password":loginInfo.password }, function(res){ if(res && res.success){ return owner.createState(res.data, callback); }else{ return callback(res.msg); } }, 'json'); };
appServerAddressPrefix 是自己的后台服務器的地址,如下:
var appServerAddressPrefix = "http://192.168.1.6:8088";
createState 以及setState是保存登錄的token等信息到本地:
owner.createState = function(token, callback) { var state = owner.getState(); state.account = token.username; state.token = token.tokenstr; owner.setState(state); return callback(); };
/** * 設置當前狀態 **/ owner.setState = function(state) { state = state || {}; localStorage.setItem('$state', JSON.stringify(state)); //var settings = owner.getSettings(); //settings.gestures = ''; //owner.setSettings(settings); };
2.重寫login.html的toMain()方法:也就是在跳轉之前先對本地的token進行驗證:
var toMain = function() { //跳轉之前先驗證token var stateText = localStorage.getItem('$state') || "{}"; var state = JSON.parse(stateText); var isValidToken = true; if(state != null && state.token!=null){ //如果token生效就加載頁面,如果不生效就返回到登錄頁面 $.ajax({ url:appServerAddressPrefix+"/mobile/token/validateToken.html", async:false, data:{"tokenStr":state.token}, success:function(res){ if(res && res.success){ //根據tokenStr判斷是否需要更新本地token if(res.data.tokenstr != state.token){ $.alert("需要更新Token"); state.token = res.data.tokenstr; localStorage.setItem('$state', JSON.stringify(state)); } }else{ //提示token無效且將標記設為false不進行跳轉 $.toast(res.msg); isValidToken = false; } }, dataType:'json' }); } if(!isValidToken){//如果Token無效就返回不跳轉主頁面 return; } //進行頁面跳轉 $.fire(mainPage, 'show', null); setTimeout(function() { $.openWindow({ id: 'main', show: { aniShow: 'pop-in' }, waiting: { autoShow: false } }); }, 0); };
2.下拉刷新的思想:
在mui.init事件中聲明下拉刷新,如果是最后一頁就結束下拉刷新事件。 mui.init...的auto:true,是頁面初始化自動調用一次對應的事件。
頁面:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello MUI</title> <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <!--標准mui.css--> <link rel="stylesheet" href="../css/mui.min.css"> <!--App自定義的css--> <link rel="stylesheet" type="text/css" href="../css/app.css"/> <style> .mui-content>.mui-table-view:first-child { margin-top: -1px; } #pullrefresh{ margin-top: 80px; } .mui-table-view div{ float: left; } </style> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">用戶列表</h1> </header> <div class="mui-content"> <div style="margin:5px 0px ;"> <table width="100%" class="table" id="tablevalue"> <tr> <th width=25%>序號</th> <th width=25%>姓名</th> <th width=25%>性別</th> <th width=25%>地址</th> </tr> </table> </div> <!--下拉刷新容器--> <div id="pullrefresh" class="mui-scroll-wrapper"> <div class="mui-scroll"> <!--數據列表--> <ul class="mui-table-view mui-table-view-chevron"> <!--<li class="mui-table-view-cell"> <a class="mui-navigate-right">init</a> </li>--> </ul> </div> </div> </div> </body> <script src="../js/mui.min.js" type="text/javascript" charset="utf-8"></script> <script src="../js/constants.js" type="text/javascript" charset="utf-8"></script> <script src="../js/user.js" type="text/javascript" charset="utf-8"></script> </html>
頁面對應的JS:
mui.init({ pullRefresh: { container: '#pullrefresh', down: {//下拉刷新未實現 style:'circle', callback: pulldownRefresh }, up: {//上拉刷新實現 auto:true, contentrefresh: '正在加載...', callback: pullupRefresh } } }); var downPage = 0,upPage=0;//標記下拉、上滑動的頁號 var count = 0; function pullupRefresh() { setTimeout(function() {//將主要代碼有個延遲是為了有個緩沖的過程,當然可以不延遲,顯示效果不太好 upPage++; mui.post(appServerAddressPrefix+"/mobile/user/getUsers.html",{"pageNum":upPage},function(res){ //1.添加數據 addData(res.list,res.isFirstPage); //2.刷新數據,如果是最后一頁停止加載加載事件 /*if(res.isLastPage){//刷新數據,如果是最后一頁就停止上滑刷新事件 mui('#pullrefresh').pullRefresh().endPullupToRefresh(true); }else{ mui('#pullrefresh').pullRefresh().endPullupToRefresh(); }*/ mui('#pullrefresh').pullRefresh().endPullupToRefresh(res.isLastPage); },'json'); }, 1500); } function addData(datas,isfirstpage) { var table = document.body.querySelector('.mui-table-view'); var cells = document.body.querySelectorAll('.mui-table-view-cell'); for(var i = cells.length, len = i + datas.length; i < len; i++) { var li = document.createElement('li'); var user = datas[i-cells.length]; li.className = 'mui-table-view-cell'; li.innerHTML = '<a class="mui-navigate-right">'+user.username +' '+user.sex+ '</a>'; //下拉刷新,新紀錄插到最前面; table.insertBefore(li, table.firstChild); } if(!isfirstpage){//如果不是第一頁就提示加載 了數據 mui.toast("為你加載了"+datas.length+"條數據!"); } } /** * 下拉刷新具體業務實現 */ function pulldownRefresh() { setTimeout(function() { addData(); mui('#pullrefresh').pullRefresh().endPulldownToRefresh(); mui.toast("為你推薦了5篇文章"); }, 1500); }
效果如下:
app代碼git地址:https://github.com/qiao-zhi/MyApp.git
后端git與上面的web項目是同一個地址:https://github.com/qiao-zhi/springboot-ssm.git
至此實現了一個簡單的app,在使用的時候我們可以通過HBuilder打一個apk安裝包出來,然后通過連接進行下載或者通過二維碼掃描進行下載。安卓手機訪問http連接后綴是apk的時候會自動安裝軟件。
========HBuilder中app的打包以及手機端的安裝========
1.右鍵項目:發行->發行為原生安裝包
2.然后選擇參數配置去補全參數之后進行配置
3.填寫應用的appid、選擇圖標等信息。====一般需要修改默認的log、啟動的flash等替代默認圖標,當然需要配置一些權限。如果第三方登錄還需要在SDK設置選項卡設置appid等信息。當然需要在對應的應用程序后台建立對應的app實現對接。
4.然后保存之后再次發行為原生安裝包(這里可能需要改一下Android的包名)
5.然后等待打包即可
6.打包成功之后下載安裝包
7.下載完成之后文件如下:
8.接下來創建一個http鏈接進行訪問apk即可 (或者通過QQ、微信等將apk傳到手機都可以,只是http訪問后綴為apk的時候會自動作為app安裝軟件)
例如我將它放在自己的服務器上,然后手機端的網頁通過http訪問鏈接的時候效果如下:
http://qiaoliqiang.cn/fileDown/qlq.mytestapp2_0226180452.apk
9.手機端通過網頁訪問下載app即可,如下:
當然可以做一個二維碼來實現掃碼下載,二維碼中的內容存放上面的地址鏈接就可以了。如下面:(只能通過瀏覽器掃碼下載,不能通過微信和QQ下載,騰訊只允許掃碼安裝自己域名下的app、)
補充:關於 localStorage
localStorage 會可以將第一次請求的數據直接存儲到本地,這個相當於一個 5M 大小的針對於前端頁面的數據庫,相比於 cookie 可以節約帶寬,但是這個卻是只有在高版本的瀏覽器中才支持的。
使用 localStorage 創建一個本地存儲的 name/value 對。localStorage 用於長久保存整個網站的數據,保存的數據沒有過期時間,直到手動去刪除。localStorage 屬性是只讀的。
#保存 localStorage.setItem("key", "value"); #讀取 var lastname = localStorage.getItem("key"); #刪除 localStorage.removeItem("key");
(1) localStorage的key不允許重復,會產生覆蓋
localStorage.setItem("name", "zhangsan"); localStorage.setItem("name", "lisi"); localStorage.setItem("age", "25"); console.log(localStorage);
結果:
(2)localStorage的數據會永久生效。同一瀏覽器,標簽頁全部共享,它是直接存到電腦硬盤上的。
(3)sessionStorage是當前會話有效,用法同localStorage。