<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
使用者註冊功能是每一個系統的入口門面功能,很多人可能會以為很簡單,不就是一個簡單的CRUD嗎?其實不然,要把前後端功能都做出來,頁面跳轉也沒問題,還真不簡單。這次筆者做這麼一個看似簡單的使用者註冊功能就花了足足兩天多時間,中間偵錯和解決Bug也花了好長時間。這次我就把自己做出的完整功能的實現過程作了一個提煉分享到我的公眾號上來。希望有需要了解如何實現使用者註冊完整過程的讀者朋友能夠仔細看一看。
說明:本文前後端程式碼的實現分別在本人之前二次開發的開源專案vue-element-admin
和vueblog
兩個專案的基礎上進行
2.1.1 介面url
http://localhost:8081/blog/upload/user/avatar
2.1.2 請求型別
POST
2.1.3 介面入參
引數名稱 | 引數型別 | 是否必傳 | 備註 |
---|---|---|---|
file | MultipartFile | 是 | 多媒體圖片檔案 |
2.1.4 介面出參
引數名稱 | 引數型別 | 範例值 | 備註 |
---|---|---|---|
status | Integer | 200 | 狀態碼:200-成功; 500-失敗 |
msg | String | “success” | 響應資訊:“success”-上傳頭像成功; "upload file failed"-上傳頭像失敗 |
data | String | vueblog2022.oss-cn-shenzhen.aliyuncs.com/avatar/63be… | 上傳頭像成功後的下載地址 |
2.2.1 介面url
http://localhost:8081/blog/user/reg
2.2.2 請求型別
POST
2.2.3 介面入參
引數名稱 | 引數型別 | 是否必填 | 備註 |
---|---|---|---|
username | String | 是 | 使用者賬號 |
nickname | String | 是 | 使用者暱稱 |
password | String | 是 | 使用者登入密碼 |
userface | String | 否 | 使用者頭像連結地址 |
phoneNum | Long | 是 | 使用者手機號碼 |
String | 否 | 使用者郵箱地址 |
2.2.3 介面出參
引數名稱 | 引數型別 | 範例值 | 備註 |
---|---|---|---|
status | Integer | 200 | 響應碼: 200-成功;500-失敗 |
msg | String | 註冊成功 | 響應訊息 |
data | Integer | 0 | 註冊成功標識:0-註冊成功;1-使用者名稱重複; null-內部服務異常 |
檔案上傳,這裡選用了阿里雲的物件儲存,需要先開通阿里雲物件儲存服務,關於如何開通阿里雲簡訊服務並將阿里雲物件儲存服務整合到SpringBoot專案中,請參考我之前釋出的文章SpringBoot專案整合阿里雲物件儲存服務實現檔案上傳
3.1.1 服務層編碼
新建OssClientService
類繼承阿里雲物件儲存服務SDK完成圖片上傳功能
@Service public class OssClientService { @Resource private OssProperties ossProperties; private static final Logger logger = LoggerFactory.getLogger(OssClientService.class); public String uploadFile(MultipartFile file){ // 建立OSSClient範例。 OSS ossClient = new OSSClientBuilder().build(ossProperties.getEndPoint(), ossProperties.getAccessKey(), ossProperties.getSecretKey()); String uuid = UUID.randomUUID().toString().replaceAll("-", ""); String objectName = "avatar/" + uuid + ".png"; String imageUrl = null; try { InputStream inputStream = file.getInputStream(); ossClient.putObject(ossProperties.getBucketName(), objectName, inputStream); imageUrl = "https://" + ossProperties.getBucketName() + "." + ossProperties.getEndPoint() + "/" + objectName; } catch (OSSException oe) { logger.error("Caught an OSSException, which means your request made it to OSS, but was rejected with an error response for some reason."); logger.error("Error Message:" + oe.getErrorMessage()); logger.error("Error Code:" + oe.getErrorCode()); logger.error("RequestId: " + oe.getRequestId()); logger.error("Host ID:" + oe.getHostId()); } catch (ClientException ce) { logger.error("Caught an ClientException, which means the client encountered a serious internal problem " + "while trying to communicate with OSS,such as not being able to access the network"); logger.error("Error Message:" + ce.getErrorMessage()); } catch (FileNotFoundException fe) { logger.error("file not found exception"); logger.error("Error Message:" + fe.getMessage(), fe); } catch (IOException exception){ logger.error("file get input stream error, caused by " + exception.getMessage(), exception); } finally { if (ossClient!=null) { ossClient.shutdown(); } } return imageUrl; } }
注意:升級到3.9.1版本後的aliyun-sdk-oss
需要在每次上傳檔案時新建一個OSS
範例, 上傳完檔案之後再呼叫shutdown
方法關閉這個範例
3.1.2 控制器層編碼
新建UploadFileController
類完成從前端接收附件引數,並呼叫OssClientService
服務實現圖片上傳
@RestController @RequestMapping("/upload") public class UploadFileController { @Resource private OssClientService ossClientService; @PostMapping("/user/avatar") @ApiOperation(value = "userAvatar", notes = "使用者上傳頭像介面", produces = "application/octet-stream", consumes = "application/json") public RespBean uploadUserAvatar(HttpServletRequest request){ MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; // 獲取上傳檔案物件 MultipartFile file = multipartRequest.getFile("file"); RespBean respBean = new RespBean(); String downloadUrl = ossClientService.uploadFile(file); if (!StringUtils.isEmpty(downloadUrl)) { respBean.setStatus(200); respBean.setMsg("success"); respBean.setData(downloadUrl); } else { respBean.setStatus(500); respBean.setMsg("upload file failed"); } return respBean; } }
3.2.1 資料庫存取層編碼
在UserMapper
介面類中新增註冊使用者抽象方法
int registerUser(UserDTO user);
然後在UserMapper.xml
檔案中完成使用者資料入庫sql編寫
<insert id="registerUser" useGeneratedKeys="true" keyProperty="id" parameterType="org.sang.pojo.dto.UserDTO"> INSERT INTO user(username, nickname, password, phoneNum,email, userface, regTime,enabled) values(#{username,jdbcType=VARCHAR},#{nickname,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{phoneNum,jdbcType=BIGINT}, #{email,jdbcType=VARCHAR}, #{userface,jdbcType=VARCHAR},now(),1) </insert>
3.2.2 服務層編碼
在CustomUserDetailsService
介面類中新增註冊使用者抽象方法
int registerUser(UserDTO user);
然後在 CustomUserDetailsService
介面類的實現類UserService
類中完成使用者註冊邏輯
@Override public int registerUser(UserDTO user) { // 判斷使用者是否重複註冊 UserDTO userDTO = userMapper.loadUserByUsername(user.getUsername()); if (userDTO != null) { return 1; } //插入使用者, 插入之前先對密碼進行加密 user.setPassword(passwordEncoder.encode(user.getPassword())); user.setEnabled(1);//使用者可用 int result = userMapper.registerUser(user); //設定使用者的角色,預設都是普通使用者 List<Integer> roleIds = Arrays.asList(2); int i = rolesMapper.setUserRoles(roleIds, user.getId()); boolean b = i == roleIds.size() && result == 1; if (b) { // 註冊成功 return 0; } else { // 註冊失敗 return 2; } }
3.2.3 控制器層編碼
在LoginRegController
類中完成使用者登入介面從前端接收引數到呼叫UserService
服務類完成使用者註冊業務
@RequestMapping(value = "/login_page", method = RequestMethod.GET) @ApiOperation(value = "loginPage", notes = "尚未登入跳轉", produces = "application/json", consumes = "application/json", response = RespBean.class) public RespBean loginPage() { return new RespBean(ResponseStateConstant.UN_AUTHORIZED, "尚未登入,請登入!"); } @PostMapping("/user/reg") @ApiOperation(value = "reg", notes = "使用者註冊", produces = "application/json", consumes = "application/json", response = RespBean.class) public RespBean reg(@RequestBody UserDTO user) { int result = userService.registerUser(user); if (result == 0) { //成功 return new RespBean(ResponseStateConstant.SERVER_SUCCESS, "註冊成功!"); } else if (result == 1) { return new RespBean(ResponseStateConstant.DUPLICATE_ERROR, "使用者名稱重複,註冊失敗!"); } else { //失敗 return new RespBean(ResponseStateConstant.SERVER_ERROR, "註冊失敗!"); } }
由於以上兩個介面都是需要放開許可權控制的,因此完成以上兩個介面的編碼後還需要在security設定類WebSecurityConfig
類中支援匿名存取
只需要在configure(HttpSecurity http)
方法中新增如下幾行程式碼即可
http.authorizeRequests() .antMatchers("/user/reg").anonymous() .antMatchers("/upload/user/avatar").anonymous()
完成後端編碼後可以啟動Mysql服務和redis服務,然後執行BlogserverApplication
類中的Main方法成功後就可以通過postman工具測試介面了
在src/views
目錄下新建register
資料夾,然後在register
目錄下新建index.vue
檔案
完成使用者註冊元件編碼
這裡的檔案上傳選擇了element-ui
元件庫中的upload元件
<template> <div class="register-container"> <el-form :model="registerModel" :rules="rules" ref="registerForm" label-width="100px" class="register-form"> <el-form-item label="使用者賬號" prop="userAccount" required> <el-input v-model="registerModel.userAccount" placeholder="請輸入使用者名稱"/> </el-form-item> <el-form-item label="使用者暱稱" prop="nickName" required> <el-input v-model="registerModel.nickName" type="text" placeholder="請輸入使用者暱稱"/> </el-form-item> <el-form-item label="登入密碼" prop="password" required> <el-input v-model="registerModel.password" type="password" placeholder="請輸入密碼" suffix-icon="el-icon-lock"/> </el-form-item> <el-form-item label="確認密碼" prop="password2" required> <el-input v-model="registerModel.password2" type="password" :show-password="false" placeholder="請再次輸入密碼" suffix-icon="el-icon-lock" /> </el-form-item> <el-form-item label="頭像"> <el-upload class="avatar-uploader" :show-file-list="false" accept="image" :action="uploadAvatarUrl" :on-preview="previewAvatar" :before-upload="beforeAvartarUpload" :on-success="handleSuccessAvatar" > <img v-if="avatarUrl" :src="avatarUrl" class="avatar" /> <div v-else class="upload-btn" > <el-button>點選上傳頭像</el-button> <div slot="tip" class="el-upload__tip">只能上傳jpg/png檔案,且不超過10M</div> </div> </el-upload> </el-form-item> <el-form-item label="手機號" prop="phoneNum" required> <el-input type="tel" v-model="registerModel.phoneNum" placeholder="請輸入手機號" /> </el-form-item> <el-form-item label="郵箱" prop="email"> <el-input type="email" v-model="registerModel.email" placeholder="請輸入你的郵箱" /> </el-form-item> <el-form-item class="btn-area"> <el-button class="submit-btn" type="primary" :loading="onLoading" @click="handleRegister('registerForm')">提交</el-button> <el-button class="reset-btn" type="info" @click="resetForm('registerForm')">重置</el-button> </el-form-item> </el-form> </div> </template> <script> import { Message } from 'element-ui' import { isNumber, validatePhoneNum, validatePassword, validEmail } from '@/utils/validate' export default { name: 'register', data(){ // 密碼校驗器 const passwordValidator = (rule,value, callback) =>{ console.log(rule) if(!validatePassword(value)){ callback('密碼強度不滿足要求,密碼必須同時包含字母、數位和特殊字元,請重新輸入') } else { callback() } } // 二次密碼校驗器 const password2Validator = (rule, value, callback) => { console.log(rule) const password = this.registerModel.password if(password!=value){ callback(new Error('兩次輸入的密碼不一致')) } else { callback() } } // 手機號碼校驗器 const phoneNumValidator = (rule, value, callback)=> { console.log(rule) if(!(value.length==11 && isNumber(value))){ callback(new Error('手機號碼必須是11位數位')) } else if(!validatePhoneNum(parseInt(value))){ callback(new Error('手機號碼不合法')) } else { callback() } } // 郵件地址校驗器 const emailValidator = (rule, value, callback) => { console.log(rule) if(value!='' && !validEmail(value)){ callback(new Error('郵箱地址不合法')) } else { callback() } } // 區分本地開發環境和生產環境 let uploadAvatarUrl = '' if(window.location.host='localhost'){ uploadAvatarUrl = 'http://localhost:8081/blog/upload/user/avatar' } else { uploadAvatarUrl = 'http://www.javahsf.club:8081/blog/upload/user/avatar' } return { uploadAvatarUrl: uploadAvatarUrl, registerModel: { userAccount: '', nickName: '', password: '', password2: '', avatarSize: 32, uploadUrl: uploadUrl, phoneNum: '', email: '' }, onLoading: false, avatarUrl: '', password2Style: { dispaly: 'none', color: 'red' }, // 表單校驗規則 rules: { userAccount: [ { required: true, message: '請輸入使用者賬號', trigger: 'blur' }, { min: 2, max: 64, message: '2-64個字元', trigger: 'blur' } ], nickName: [ { required: true, message: '請輸入暱稱', trigger: 'blur' }, { min: 2, max: 64, message: '長度控制在2-64個字元',trigger: 'blur' } ], password: [ { required: true, message: '請輸入密碼', trigger: 'blur' }, { min: 6, max: 18, message: '長度控制在6-18個字元', trigger: 'blur' }, { validator: passwordValidator, trigger: 'blur' } ], password2: [ { required: true, message: '請再次輸入密碼', trigger: 'blur' }, { min: 6, max: 18, message: '長度控制在6-18個字元', trigger: 'blur' }, { validator: password2Validator, trigger: 'blur' } ], phoneNum: [ { required: true, message: '請輸入手機號', trigger: 'blur'}, { validator: phoneNumValidator, trigger: 'blur' } ], email: [ { min: 0, max: 64, message: '長度控制在64個字元'}, { validator: emailValidator, trigger: 'blur' } ] }, redirect: undefined } }, watch: { $route: { handler: function(route) { const query = route.query if (query) { this.redirect = query.redirect this.otherQuery = this.getOtherQuery(query) } }, immediate: true } }, methods: { // 圖片上傳之前校驗圖片格式和附件大小 beforeAvartarUpload(file) { console.log(file) if(!(file.type=='image/jpeg' ||file.type=='image/png')){ Message.error('頭像圖片必須是jpg或png格式') }else if(file.size/(1024*1024)>10){ Message.error('圖片大小不能超過10M') } }, // 上傳圖片預覽 previewAvatar(file){ console.log(file) }, // 圖片上傳成功回撥 handleSuccessAvatar(response){ console.log(response.data) this.avatarUrl = response.data }, // 提交註冊 handleRegister(formName){ this.$refs[formName].validate((valid=>{ if(valid){ // 表單校驗通過 const params = { username: this.registerModel.userAccount, nickname: this.registerModel.nickName, password: this.registerModel.password, phoneNum: this.registerModel.phoneNum, email: this.registerModel.email, userface: this.avatarUrl } this.onLoading = true this.$store.dispatch('user/register', params).then(res=>{ this.onLoading = true if(res.status===200){ Message.success('恭喜註冊成功,現在就可以登入系統了!') // 跳轉到登入介面 this.$router.push({ path: '/login', query: this.otherQuery }) } else { Message.error(res.msg) } }) }else{ // 表單校驗不通過,拒絕提交註冊 this.onLoading = true Message.error('使用者註冊資訊校驗不通過,請重新填寫註冊資訊') return false } })) }, // 表單重置 resetForm(formName) { this.$refs[formName].resetFields() }, getOtherQuery(query) { return Object.keys(query).reduce((acc, cur) => { if (cur !== 'redirect') { acc[cur] = query[cur] } return acc }, {}) } } } </script> <!--頁面樣式--> <style lang="scss" scoped> .register-container{ margin-top: 100px; margin-left: 10%; .el-input{ width: 60%; } .avatar-uploader .avatar{ width: 240px; height: 240px; } .el-button.submit-btn{ width: 10%; height: 40px; margin-left: 150px; margin-right: 25px; } .el-button.reset-btn{ width: 10%; height: 40px; } } </style>
src/utils/validate.js
中增加校驗密碼和手機號碼的方法
export function validatePhoneNum(phoneNum) { const reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])d{8}$/ return reg.test(phoneNum) } export function validatePassword(password) { // 強密碼:字母+數位+特殊字元 const reg = /^(?![a-zA-z]+$)(?!d+$)(?![!@#$%^&*]+$)(?![a-zA-zd]+$)(?![a-zA-z!@#$%^&*]+$)(?![d!@#$%^&*]+$)[a-zA-Zd!@#$%^&*]+$/ return reg.test(password) }
以上校驗均使用正規表示式校驗
src/api/user.js
檔案中新增使用者註冊介面方法
export function register(data) { return request({ url: '/user/reg', method: 'post', data }) }
src/store/modules/user.js
檔案中的actions
物件中增加使用者註冊行為方法
const actions = { // user register register({ commit }, registerInfo) { return new Promise((resolve, reject) => { register(registerInfo).then(response => { if (response.status === 200 && response.data.status === 200) { const resInfo = { status: response.status, msg: '註冊成功' } resolve(resInfo) } else { const resInfo = { status: response.status, msg: response.data.msg } resolve(resInfo) } }).catch(error => { console.error(error) reject(error) }) }) }, // ......省略其他已有方法 }
因為使用者註冊完之後需要跳轉到登入介面,直接在註冊頁面呼叫後臺使用者註冊介面成功後呼叫this.$router.push
方法發現無法實現頁面的跳轉效果, 因此改為在vuex
的全域性dispatch
中呼叫註冊介面
在src/router/index.js
檔案的固定路由列表中新增註冊元件的路由
import Register from '@/views/register/index' export const constantRoutes = [ { id: '0', path: '/register', component: Register, hidden: true }, //...... 省略其他路由 ]
在src/views/login/index.vue
檔案中的模板程式碼部分的登入按鈕標籤下面新增如下兩行程式碼
<div> <router-link to="/resetPass" class="forget-password">忘記密碼</router-link> <router-link class="register" to="/register">註冊賬號</router-link> </div>
同時對忘記密碼和註冊賬號兩個連結新增樣式(忘記密碼功能尚待實現)
<style lang="scss" scoped> .register, .forget-password{ width: 20%; height: 35px; color: blue; margin-right: 20px; cursor: pointer; } </style>
在路由跳轉控制檔案src/permission.js
檔案中將註冊使用者的路由新增到白名單中
const whiteList = ['/login', '/register', '/auth-redirect'] // no redirect whitelist
如果不在白名單中加上使用者註冊的路由,你會發現在使用者登入介面壓根無法跳轉到使用者註冊介面的
在啟動後端服務後,在vue-element-admin專案下通過 滑鼠右鍵->git bash進入命令控制檯
然後輸入npm run dev
專案啟動前端服務
然後在谷歌瀏覽器中輸入:http://localhost:3000/回車進入登入介面
點選下面的【註冊賬號】連結就能跳轉到用【使用者註冊】頁面
填寫好使用者註冊資訊後就可以點選下面的【提交】按鈕提交註冊了,註冊成功後系統會彈框提示使用者中註冊成功,並重新跳轉到【使用者登入】介面
本文演示了在spring-boot
專案中繼承阿里雲物件儲存sdk實現了圖片上傳和使用者提交登入兩個介面的詳細實現,同時前端使用element-ui
庫中的upload
元件呼叫後端圖片上傳介面實現了附件上傳功能,實現了一個完整的使用者登入資訊的校驗和提交註冊及註冊成功後的頁面跳轉等功能。
相信對想要了解一個系統的使用者模組是如何實現使用者的註冊以及註冊成功後的頁面跳轉的完整功能的是如何實現的讀者朋友一定會有所幫助的!
本文前後端專案程式碼git倉庫地址如下,對原始碼感興趣的讀者朋友可以克隆到本地參考
到此這篇關於SpringBoot專案整合Vue做一個完整的使用者註冊功能的文章就介紹到這了,更多相關SpringBoot專案整合Vue做一個完整的使用者註冊功能內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45