詳解SpringBoot項(xiàng)目整合Vue做一個(gè)完整的用戶注冊(cè)功能
前言
用戶注冊(cè)功能是每一個(gè)系統(tǒng)的入口門(mén)面功能,很多人可能會(huì)以為很簡(jiǎn)單,不就是一個(gè)簡(jiǎn)單的CRUD嗎?其實(shí)不然,要把前后端功能都做出來(lái),頁(yè)面跳轉(zhuǎn)也沒(méi)問(wèn)題,還真不簡(jiǎn)單。這次筆者做這么一個(gè)看似簡(jiǎn)單的用戶注冊(cè)功能就花了足足兩天多時(shí)間,中間調(diào)試和解決Bug也花了好長(zhǎng)時(shí)間。這次我就把自己做出的完整功能的實(shí)現(xiàn)過(guò)程作了一個(gè)提煉分享到我的公眾號(hào)上來(lái)。希望有需要了解如何實(shí)現(xiàn)用戶注冊(cè)完整過(guò)程的讀者朋友能夠仔細(xì)看一看。
說(shuō)明:本文前后端代碼的實(shí)現(xiàn)分別在本人之前二次開(kāi)發(fā)的開(kāi)源項(xiàng)目vue-element-admin
和vueblog
兩個(gè)項(xiàng)目的基礎(chǔ)上進(jìn)行
1 實(shí)現(xiàn)用戶注冊(cè)流程
1.1 用戶注冊(cè)完整流程
1.2 用戶注冊(cè)信息及校驗(yàn)
2 后臺(tái)接口設(shè)計(jì)
2.1 上傳頭像接口
2.1.1 接口url
http://localhost:8081/blog/upload/user/avatar
2.1.2 請(qǐng)求類型
POST
2.1.3 接口入?yún)?/p>
參數(shù)名稱 | 參數(shù)類型 | 是否必傳 | 備注 |
---|---|---|---|
file | MultipartFile | 是 | 多媒體圖片文件 |
2.1.4 接口出參
參數(shù)名稱 | 參數(shù)類型 | 示例值 | 備注 |
---|---|---|---|
status | Integer | 200 | 狀態(tài)碼:200-成功; 500-失敗 |
msg | String | “success” | 響應(yīng)信息:“success”-上傳頭像成功; "upload file failed"-上傳頭像失敗 |
data | String | vueblog2022.oss-cn-shenzhen.aliyuncs.com/avatar/63be… | 上傳頭像成功后的下載地址 |
2.2 用戶注冊(cè)接口
2.2.1 接口url
http://localhost:8081/blog/user/reg
2.2.2 請(qǐng)求類型
POST
2.2.3 接口入?yún)?/p>
參數(shù)名稱 | 參數(shù)類型 | 是否必填 | 備注 |
---|---|---|---|
username | String | 是 | 用戶賬號(hào) |
nickname | String | 是 | 用戶昵稱 |
password | String | 是 | 用戶登錄密碼 |
userface | String | 否 | 用戶頭像鏈接地址 |
phoneNum | Long | 是 | 用戶手機(jī)號(hào)碼 |
String | 否 | 用戶郵箱地址 |
2.2.3 接口出參
參數(shù)名稱 | 參數(shù)類型 | 示例值 | 備注 |
---|---|---|---|
status | Integer | 200 | 響應(yīng)碼: 200-成功;500-失敗 |
msg | String | 注冊(cè)成功 | 響應(yīng)消息 |
data | Integer | 0 | 注冊(cè)成功標(biāo)識(shí):0-注冊(cè)成功;1-用戶名重復(fù); null-內(nèi)部服務(wù)異常 |
3 后端代碼實(shí)現(xiàn)
3.1 用戶頭像上傳接口編碼實(shí)現(xiàn)
文件上傳,這里選用了阿里云的對(duì)象存儲(chǔ),需要先開(kāi)通阿里云對(duì)象存儲(chǔ)服務(wù),關(guān)于如何開(kāi)通阿里云短信服務(wù)并將阿里云對(duì)象存儲(chǔ)服務(wù)集成到SpringBoot項(xiàng)目中,請(qǐng)參考我之前發(fā)布的文章SpringBoot項(xiàng)目集成阿里云對(duì)象存儲(chǔ)服務(wù)實(shí)現(xiàn)文件上傳
3.1.1 服務(wù)層編碼
新建OssClientService
類繼承阿里云對(duì)象存儲(chǔ)服務(wù)SDK完成圖片上傳功能
@Service public class OssClientService { @Resource private OssProperties ossProperties; private static final Logger logger = LoggerFactory.getLogger(OssClientService.class); public String uploadFile(MultipartFile file){ // 創(chuàng)建OSSClient實(shí)例。 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; } }
注意:升級(jí)到3.9.1版本后的aliyun-sdk-oss
需要在每次上傳文件時(shí)新建一個(gè)OSS
實(shí)例, 上傳完文件之后再調(diào)用shutdown
方法關(guān)閉這個(gè)實(shí)例
3.1.2 控制器層編碼
新建UploadFileController
類完成從前端接收附件參數(shù),并調(diào)用OssClientService
服務(wù)實(shí)現(xiàn)圖片上傳
@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; // 獲取上傳文件對(duì)象 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 用戶注冊(cè)接口
3.2.1 數(shù)據(jù)庫(kù)訪問(wèn)層編碼
在UserMapper
接口類中新增注冊(cè)用戶抽象方法
int registerUser(UserDTO user);
然后在UserMapper.xml
文件中完成用戶數(shù)據(jù)入庫(kù)sql編寫(xiě)
<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 服務(wù)層編碼
在CustomUserDetailsService
接口類中添加注冊(cè)用戶抽象方法
int registerUser(UserDTO user);
然后在 CustomUserDetailsService
接口類的實(shí)現(xiàn)類UserService
類中完成用戶注冊(cè)邏輯
@Override public int registerUser(UserDTO user) { // 判斷用戶是否重復(fù)注冊(cè) UserDTO userDTO = userMapper.loadUserByUsername(user.getUsername()); if (userDTO != null) { return 1; } //插入用戶, 插入之前先對(duì)密碼進(jìn)行加密 user.setPassword(passwordEncoder.encode(user.getPassword())); user.setEnabled(1);//用戶可用 int result = userMapper.registerUser(user); //配置用戶的角色,默認(rèn)都是普通用戶 List<Integer> roleIds = Arrays.asList(2); int i = rolesMapper.setUserRoles(roleIds, user.getId()); boolean b = i == roleIds.size() && result == 1; if (b) { // 注冊(cè)成功 return 0; } else { // 注冊(cè)失敗 return 2; } }
3.2.3 控制器層編碼
在LoginRegController
類中完成用戶登錄接口從前端接收參數(shù)到調(diào)用UserService
服務(wù)類完成用戶注冊(cè)業(yè)務(wù)
@RequestMapping(value = "/login_page", method = RequestMethod.GET) @ApiOperation(value = "loginPage", notes = "尚未登錄跳轉(zhuǎn)", produces = "application/json", consumes = "application/json", response = RespBean.class) public RespBean loginPage() { return new RespBean(ResponseStateConstant.UN_AUTHORIZED, "尚未登錄,請(qǐng)登錄!"); } @PostMapping("/user/reg") @ApiOperation(value = "reg", notes = "用戶注冊(cè)", 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, "注冊(cè)成功!"); } else if (result == 1) { return new RespBean(ResponseStateConstant.DUPLICATE_ERROR, "用戶名重復(fù),注冊(cè)失敗!"); } else { //失敗 return new RespBean(ResponseStateConstant.SERVER_ERROR, "注冊(cè)失敗!"); } }
由于以上兩個(gè)接口都是需要放開(kāi)權(quán)限控制的,因此完成以上兩個(gè)接口的編碼后還需要在security配置類WebSecurityConfig
類中支持匿名訪問(wèn)
只需要在configure(HttpSecurity http)
方法中添加如下幾行代碼即可
http.authorizeRequests() .antMatchers("/user/reg").anonymous() .antMatchers("/upload/user/avatar").anonymous()
完成后端編碼后可以啟動(dòng)Mysql服務(wù)和redis服務(wù),然后運(yùn)行BlogserverApplication
類中的Main方法成功后就可以通過(guò)postman工具測(cè)試接口了
4 前端代碼實(shí)現(xiàn)
4.1 完成用戶注冊(cè)界面vue組件編碼
在src/views
目錄下新建register
文件夾,然后在register
目錄下新建index.vue
文件
完成用戶注冊(cè)組件編碼
這里的文件上傳選擇了element-ui
組件庫(kù)中的upload組件
<template> <div class="register-container"> <el-form :model="registerModel" :rules="rules" ref="registerForm" label-width="100px" class="register-form"> <el-form-item label="用戶賬號(hào)" prop="userAccount" required> <el-input v-model="registerModel.userAccount" placeholder="請(qǐng)輸入用戶名"/> </el-form-item> <el-form-item label="用戶昵稱" prop="nickName" required> <el-input v-model="registerModel.nickName" type="text" placeholder="請(qǐng)輸入用戶昵稱"/> </el-form-item> <el-form-item label="登錄密碼" prop="password" required> <el-input v-model="registerModel.password" type="password" placeholder="請(qǐng)輸入密碼" suffix-icon="el-icon-lock"/> </el-form-item> <el-form-item label="確認(rèn)密碼" prop="password2" required> <el-input v-model="registerModel.password2" type="password" :show-password="false" placeholder="請(qǐng)?jiān)俅屋斎朊艽a" 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>點(diǎn)擊上傳頭像</el-button> <div slot="tip" class="el-upload__tip">只能上傳jpg/png文件,且不超過(guò)10M</div> </div> </el-upload> </el-form-item> <el-form-item label="手機(jī)號(hào)" prop="phoneNum" required> <el-input type="tel" v-model="registerModel.phoneNum" placeholder="請(qǐng)輸入手機(jī)號(hào)" /> </el-form-item> <el-form-item label="郵箱" prop="email"> <el-input type="email" v-model="registerModel.email" placeholder="請(qǐng)輸入你的郵箱" /> </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(){ // 密碼校驗(yàn)器 const passwordValidator = (rule,value, callback) =>{ console.log(rule) if(!validatePassword(value)){ callback('密碼強(qiáng)度不滿足要求,密碼必須同時(shí)包含字母、數(shù)字和特殊字符,請(qǐng)重新輸入') } else { callback() } } // 二次密碼校驗(yàn)器 const password2Validator = (rule, value, callback) => { console.log(rule) const password = this.registerModel.password if(password!=value){ callback(new Error('兩次輸入的密碼不一致')) } else { callback() } } // 手機(jī)號(hào)碼校驗(yàn)器 const phoneNumValidator = (rule, value, callback)=> { console.log(rule) if(!(value.length==11 && isNumber(value))){ callback(new Error('手機(jī)號(hào)碼必須是11位數(shù)字')) } else if(!validatePhoneNum(parseInt(value))){ callback(new Error('手機(jī)號(hào)碼不合法')) } else { callback() } } // 郵件地址校驗(yàn)器 const emailValidator = (rule, value, callback) => { console.log(rule) if(value!='' && !validEmail(value)){ callback(new Error('郵箱地址不合法')) } else { callback() } } // 區(qū)分本地開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境 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' }, // 表單校驗(yàn)規(guī)則 rules: { userAccount: [ { required: true, message: '請(qǐng)輸入用戶賬號(hào)', trigger: 'blur' }, { min: 2, max: 64, message: '2-64個(gè)字符', trigger: 'blur' } ], nickName: [ { required: true, message: '請(qǐng)輸入昵稱', trigger: 'blur' }, { min: 2, max: 64, message: '長(zhǎng)度控制在2-64個(gè)字符',trigger: 'blur' } ], password: [ { required: true, message: '請(qǐng)輸入密碼', trigger: 'blur' }, { min: 6, max: 18, message: '長(zhǎng)度控制在6-18個(gè)字符', trigger: 'blur' }, { validator: passwordValidator, trigger: 'blur' } ], password2: [ { required: true, message: '請(qǐng)?jiān)俅屋斎朊艽a', trigger: 'blur' }, { min: 6, max: 18, message: '長(zhǎng)度控制在6-18個(gè)字符', trigger: 'blur' }, { validator: password2Validator, trigger: 'blur' } ], phoneNum: [ { required: true, message: '請(qǐng)輸入手機(jī)號(hào)', trigger: 'blur'}, { validator: phoneNumValidator, trigger: 'blur' } ], email: [ { min: 0, max: 64, message: '長(zhǎng)度控制在64個(gè)字符'}, { 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: { // 圖片上傳之前校驗(yàn)圖片格式和附件大小 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('圖片大小不能超過(guò)10M') } }, // 上傳圖片預(yù)覽 previewAvatar(file){ console.log(file) }, // 圖片上傳成功回調(diào) handleSuccessAvatar(response){ console.log(response.data) this.avatarUrl = response.data }, // 提交注冊(cè) handleRegister(formName){ this.$refs[formName].validate((valid=>{ if(valid){ // 表單校驗(yàn)通過(guò) 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('恭喜注冊(cè)成功,現(xiàn)在就可以登錄系統(tǒng)了!') // 跳轉(zhuǎn)到登錄界面 this.$router.push({ path: '/login', query: this.otherQuery }) } else { Message.error(res.msg) } }) }else{ // 表單校驗(yàn)不通過(guò),拒絕提交注冊(cè) this.onLoading = true Message.error('用戶注冊(cè)信息校驗(yàn)不通過(guò),請(qǐng)重新填寫(xiě)注冊(cè)信息') 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> <!--頁(yè)面樣式--> <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>
4.2 工具類中增加校驗(yàn)方法
src/utils/validate.js
中增加校驗(yàn)密碼和手機(jī)號(hào)碼的方法
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) { // 強(qiáng)密碼:字母+數(shù)字+特殊字符 const reg = /^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&*]+$)(?![\d!@#$%^&*]+$)[a-zA-Z\d!@#$%^&*]+$/ return reg.test(password) }
以上校驗(yàn)均使用正則表達(dá)式校驗(yàn)
4.3 API文件中添加用戶注冊(cè)方法
src/api/user.js
文件中新增用戶注冊(cè)接口方法
export function register(data) { return request({ url: '/user/reg', method: 'post', data }) }
4.4 全局方法中添加用戶注冊(cè)方法
src/store/modules/user.js
文件中的actions
對(duì)象中增加用戶注冊(cè)行為方法
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: '注冊(cè)成功' } resolve(resInfo) } else { const resInfo = { status: response.status, msg: response.data.msg } resolve(resInfo) } }).catch(error => { console.error(error) reject(error) }) }) }, // ......省略其他已有方法 }
因?yàn)橛脩糇?cè)完之后需要跳轉(zhuǎn)到登錄界面,直接在注冊(cè)頁(yè)面調(diào)用后臺(tái)用戶注冊(cè)接口成功后調(diào)用this.$router.push
方法發(fā)現(xiàn)無(wú)法實(shí)現(xiàn)頁(yè)面的跳轉(zhuǎn)效果, 因此改為在vuex
的全局dispatch
中調(diào)用注冊(cè)接口
4.5 路由列表中添加用戶注冊(cè)組件
在src/router/index.js
文件的固定路由列表中添加注冊(cè)組件的路由
import Register from '@/views/register/index' export const constantRoutes = [ { id: '0', path: '/register', component: Register, hidden: true }, //...... 省略其他路由 ]
4.6 登錄組件中添加用戶注冊(cè)的跳轉(zhuǎn)鏈接
在src/views/login/index.vue
文件中的模板代碼部分的登錄按鈕標(biāo)簽下面添加如下兩行代碼
<div> <router-link to="/resetPass" class="forget-password">忘記密碼</router-link> <router-link class="register" to="/register">注冊(cè)賬號(hào)</router-link> </div>
同時(shí)對(duì)忘記密碼和注冊(cè)賬號(hào)兩個(gè)鏈接添加樣式(忘記密碼功能尚待實(shí)現(xiàn))
<style lang="scss" scoped> .register, .forget-password{ width: 20%; height: 35px; color: blue; margin-right: 20px; cursor: pointer; } </style>
4.7 路由跳轉(zhuǎn)控制中添加白名單
在路由跳轉(zhuǎn)控制文件src/permission.js
文件中將注冊(cè)用戶的路由添加到白名單中
const whiteList = ['/login', '/register', '/auth-redirect'] // no redirect whitelist
如果不在白名單中加上用戶注冊(cè)的路由,你會(huì)發(fā)現(xiàn)在用戶登錄界面壓根無(wú)法跳轉(zhuǎn)到用戶注冊(cè)界面的
5 效果體驗(yàn)
在啟動(dòng)后端服務(wù)后,在vue-element-admin項(xiàng)目下通過(guò) 鼠標(biāo)右鍵->git bash進(jìn)入命令控制臺(tái)
然后輸入npm run dev
項(xiàng)目啟動(dòng)前端服務(wù)
然后在谷歌瀏覽器中輸入:http://localhost:3000/回車(chē)進(jìn)入登錄界面
點(diǎn)擊下面的【注冊(cè)賬號(hào)】鏈接就能跳轉(zhuǎn)到用【用戶注冊(cè)】頁(yè)面
填寫(xiě)好用戶注冊(cè)信息后就可以點(diǎn)擊下面的【提交】按鈕提交注冊(cè)了,注冊(cè)成功后系統(tǒng)會(huì)彈框提示用戶中注冊(cè)成功,并重新跳轉(zhuǎn)到【用戶登錄】界面
6 寫(xiě)在最后
本文演示了在spring-boot
項(xiàng)目中繼承阿里云對(duì)象存儲(chǔ)sdk實(shí)現(xiàn)了圖片上傳和用戶提交登錄兩個(gè)接口的詳細(xì)實(shí)現(xiàn),同時(shí)前端使用element-ui
庫(kù)中的upload
組件調(diào)用后端圖片上傳接口實(shí)現(xiàn)了附件上傳功能,實(shí)現(xiàn)了一個(gè)完整的用戶登錄信息的校驗(yàn)和提交注冊(cè)及注冊(cè)成功后的頁(yè)面跳轉(zhuǎn)等功能。
相信對(duì)想要了解一個(gè)系統(tǒng)的用戶模塊是如何實(shí)現(xiàn)用戶的注冊(cè)以及注冊(cè)成功后的頁(yè)面跳轉(zhuǎn)的完整功能的是如何實(shí)現(xiàn)的讀者朋友一定會(huì)有所幫助的!
本文前后端項(xiàng)目代碼git倉(cāng)庫(kù)地址如下,對(duì)源碼感興趣的讀者朋友可以克隆到本地參考
blogserver項(xiàng)目gitee倉(cāng)庫(kù)地址
vue-element-admin項(xiàng)目gitee倉(cāng)庫(kù)地址
到此這篇關(guān)于SpringBoot項(xiàng)目整合Vue做一個(gè)完整的用戶注冊(cè)功能的文章就介紹到這了,更多相關(guān)SpringBoot項(xiàng)目整合Vue做一個(gè)完整的用戶注冊(cè)功能內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 詳解springboot?springsecuroty中的注銷和權(quán)限控制問(wèn)題
- SpringBoot使用Spring Security實(shí)現(xiàn)登錄注銷功能
- SpringBoot--- SpringSecurity進(jìn)行注銷權(quán)限控制的配置方法
- Vue+springboot批量刪除功能實(shí)現(xiàn)代碼
- springboot和vue前后端交互的實(shí)現(xiàn)示例
- SpringBoot3結(jié)合Vue3實(shí)現(xiàn)用戶登錄功能
- 基于SpringBoot和Vue3的博客平臺(tái)的用戶注冊(cè)與登錄功能實(shí)現(xiàn)
- SpringBoot和Vue.js實(shí)現(xiàn)的前后端分離的用戶權(quán)限管理系統(tǒng)
- Vue結(jié)合Springboot實(shí)現(xiàn)用戶列表單頁(yè)面(前后端分離)
- vue+springboot用戶注銷功能實(shí)現(xiàn)代碼
相關(guān)文章
SpringBoot調(diào)用公共模塊的自定義注解失效的解決
這篇文章主要介紹了SpringBoot調(diào)用公共模塊的自定義注解失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02Java內(nèi)存模型(JMM)及happens-before原理
這篇文章主要介紹了java內(nèi)存模型(JMM)及happens-before原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04如何為Spring Cloud Gateway加上全局過(guò)濾器
這篇文章主要介紹了如何為Spring Cloud Gateway加上全局過(guò)濾器,幫助大家更好得理解和學(xué)習(xí)使用Gateway,感興趣的朋友可以了解下2021-03-03Springboot如何使用mybatis實(shí)現(xiàn)攔截SQL分頁(yè)
這篇文章主要介紹了Springboot使用mybatis實(shí)現(xiàn)攔截SQL分頁(yè),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06淺談如何優(yōu)雅地停止Spring Boot應(yīng)用
這篇文章主要介紹了淺談如何優(yōu)雅地停止Spring Boot應(yīng)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Spring中ApplicationListener的使用解析
這篇文章主要介紹了Spring中ApplicationListener的使用解析,ApplicationContext事件機(jī)制是觀察者設(shè)計(jì)模式的實(shí)現(xiàn),通過(guò)ApplicationEvent類和ApplicationListener接口,需要的朋友可以參考下2023-12-12解決mybatis-plus3.1.1版本使用lambda表達(dá)式查詢報(bào)錯(cuò)的方法
這篇文章主要介紹了解決mybatis-plus3.1.1版本使用lambda表達(dá)式查詢報(bào)錯(cuò)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08