GVKun编程网logo

SpringBoot+Vue实现用户头像修改功能(vue头像上传和修改)

11

最近很多小伙伴都在问SpringBoot+Vue实现用户头像修改功能和vue头像上传和修改这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展31.springboot+springc

最近很多小伙伴都在问SpringBoot+Vue实现用户头像修改功能vue头像上传和修改这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展31. springboot+springcloud+vue b2b2c 商城之使用Turbine实现集群监控、Koa & Mongoose & Vue实现前后端分离--11更新用户头像、spring boot+vue实现爬取各大平台每日热榜数据功能、Springboot Vue Login(从零开始实现Springboot+Vue登录)等相关知识,下面开始了哦!

本文目录一览:

SpringBoot+Vue实现用户头像修改功能(vue头像上传和修改)

SpringBoot+Vue实现用户头像修改功能(vue头像上传和修改)

点击上方 web项目开发选择 设为星标

优质文章,及时送达





效果图

登录页面

上传更改头像页面


更改成功页面





环境介绍

JDK:1.8

数据库:Mysql5.6

前端:Vue

后端:SpringBoot





完整源码获取


扫码关注回复【更换用户头像】获取源码


如果你在运行这个代码的过程中有遇到问题,请加小编微信xxf960513,我拉你进对应微信学习群!!帮助你快速掌握这个功能代码!





核心代码介绍

RegistCtrler.class

package com.lc.changeimage.demo.controller;import com.lc.changeimage.demo.exception.SystemErrorType;import com.lc.changeimage.demo.rest.CodeMsg;import com.lc.changeimage.demo.rest.Result;import com.lc.changeimage.demo.rest.ServerResponse;import com.lc.changeimage.demo.service.UserRegistService;import com.lc.changeimage.demo.util.ImagesUtil;import com.lc.changeimage.demo.vo.User;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.transaction.annotation.Transactional;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.validation.Valid;import java.util.Map;@RestController@CrossOriginpublic class RegistCtrler { @Autowired private UserRegistService userRegistService; @ApiOperation(value="用户注册") @PostMapping("/regist") public Object regist(@Valid User vo) { try { return userRegistService.regist(vo); } catch (Exception e) { e.printStackTrace(); } return Result.error(CodeMsg.SERVER_ERROR); } @Transactional @ApiOperation(value="用户激活") @GetMapping("/active") @ApiImplicitParams({ @ApiImplicitParam(name = "code", value = "激活码",dataTypeClass = String.class,required = true) }) public Object active(@RequestParam("code") String code) { try { return userRegistService.update(code); } catch (Exception e) { e.printStackTrace(); } return Result.error(CodeMsg.SERVER_ERROR); } @ApiOperation(value="用户登录") @PostMapping("/active") @ApiImplicitParams({ @ApiImplicitParam(name = "username", value = "用户名",dataTypeClass = String.class,required = true), @ApiImplicitParam(name = "pwd", value = "密码",dataTypeClass = String.class,required = true) }) public Object login(@RequestParam("username") String username,@RequestParam("pwd") String pwd) { try { return userRegistService.login(username, pwd); } catch (Exception e) { e.printStackTrace(); } return Result.error(CodeMsg.SERVER_ERROR); } @ApiOperation(value = "图片上传", notes = "图片上传") @PostMapping("/uploadImages") public ServerResponse<Map> uploadImages(@RequestParam("file") MultipartFile file, HttpServletRequest request, HttpServletResponse response){ try { //String realPath = request.getSession().getServletContext().getRealPath("/"); String s = ImagesUtil.saveImages2(file); return ServerResponse.success(s); } catch (Exception ex) { ex.printStackTrace(); return ServerResponse.fail(SystemErrorType.SYSTEM_ERROR); } } @ApiOperation(value = "更新头像", notes = "更新头像") @PostMapping("/updateUser") public ServerResponse<Map> updateUser(@Valid User user){ try { userRegistService.updateUser(user); return ServerResponse.success(); } catch (Exception ex) { ex.printStackTrace(); return ServerResponse.fail(SystemErrorType.SYSTEM_ERROR); } }}

UserRegistServiceImpl.class

package com.lc.changeimage.demo.service.impl;import com.lc.changeimage.demo.mapper.UserMapper;import com.lc.changeimage.demo.rest.CodeMsg;import com.lc.changeimage.demo.rest.Result;import com.lc.changeimage.demo.service.UserRegistService;import com.lc.changeimage.demo.util.MailUtil;import com.lc.changeimage.demo.vo.User;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.UUID;@Servicepublic class UserRegistServiceImpl implements UserRegistService{ private Logger logger = LoggerFactory.getLogger(UserRegistServiceImpl.class); @Autowired private UserMapper userMapper; @Override public Object regist(User vo) { //未激活 final Integer zt = 0; //生成 验证码 code String code = UUID.randomUUID().toString().replace("-", ""); vo.setCode(code); vo.setZt(zt); vo.setCreatedate(new Date()); User findByUserName = userMapper.findByUserName(vo.getUsername()); if(null != findByUserName) { logger.error("error:注册用户名:[{}],已存在",vo.getUsername()); return Result.error(CodeMsg.USEREXISTS); } userMapper.insert(vo); try { new MailUtil(vo.getEmail(), code).run(); } catch (Exception e) { logger.error("error:发送邮件失败"); throw new RuntimeException("发送邮件失败"); } return Result.error(CodeMsg.REGIST_SUCCESS); } @Override public Object update(String code) { userMapper.update(code); return Result.error(CodeMsg.ACTIVE_SUCCESS); } @Override public int updateUser(User vo) { return userMapper.updateUser(vo); } @Override public Object login(String username, String pwd) { Map<String,String> map = new HashMap<>(3); map.put("username", username); map.put("pwd", pwd); User findByNameAndPwd = userMapper.findByNameAndPwd(map); if(null == findByNameAndPwd) { return Result.error(CodeMsg.USENOTREXISTS); } return Result.success(findByNameAndPwd); }}

ImagesUtil.class

package com.lc.changeimage.demo.util;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;import java.text.SimpleDateFormat;import java.util.Date;public class ImagesUtil { private static String FILEPATH = "/ui/images/"; public static String saveImages2(MultipartFile file) throws Exception { File filePath = new File(FILEPATH); if (!filePath.exists()) { filePath.mkdirs(); } file.transferTo(new File(FILEPATH+file.getOriginalFilename())); return FILEPATH+file.getOriginalFilename(); } public static String saveImages(String name, String path, InputStream fileInputStream) throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); String fileName = path + sdf.format(new Date()) + name; File file = new File(path + sdf.format(new Date())); if (!file.exists()) { file.mkdirs(); } createFile(fileName, fileInputStream); return fileName; } public static void createFile(String fileName, InputStream fileInputStream) throws Exception { FileOutputStream out = null; try { out = new FileOutputStream(fileName); byte[] b = new byte[1024]; int num; while ((num = fileInputStream.read(b)) > -1) { out.write(b, 0, num); } } finally { if (out != null) { out.close(); } } }}
UserMapper.interface
package com.lc.changeimage.demo.mapper;import com.lc.changeimage.demo.vo.User;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Select;import org.apache.ibatis.annotations.Update;import java.util.Map;/** * Copyright @ 2020 Zonlyn. All rights reserved. @Description: 该类的功能描述 * * @version: v1.0.0 * @author: ducl * @date: 2020年7月7日 下午8:54:57 * <p>Modification History: Date Author Version Description * ---------------------------------------------------------* 2020年7月7日 ducl v1.0.0 修改原因 */@Mapperpublic interface UserMapper { @Insert( "INSERT INTO T_USER(USERNAME,PWD,ZT,CREATEDATE,EMAIL,CODE,IMAGEPATH)\n" + "\tVALUES\n" + "\t(\n" + "\t#{username,jdbcType=VARCHAR},#{pwd,jdbcType=VARCHAR},\n" + "\t#{zt,jdbcType=INTEGER},#{createdate,jdbcType=TIMESTAMP},\n" + "\t#{email,jdbcType=VARCHAR},#{code,jdbcType=VARCHAR},\n" + "\t#{imagepath,jdbcType=VARCHAR}\n" + "\t)") void insert(User vo);
@Update("update T_USER SET IMAGEPATH = #{imagepath} WHERE USERNAME = #{username} ")  int updateUser(User user); @Update("UPDATE T_USER SET ZT = 1 WHERE CODE = #{code}")  void update(String code); @Select("SELECT * FROM T_USER WHERE USERNAME = #{username}")  User findByUserName(String userName); @Select("SELECT * FROM T_USER WHERE USERNAME = #{username} and PWD = #{pwd}")  User findByNameAndPwd(Map<String, String> map);}
application.properties
server.port=8005#server.servlet.context-path=/spring.datasource.url=jdbc:mysql://localho:3306/tests?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaispring.datasource.username=rootspring.datasource.password=rootspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#\u9A7C\u5CF0\u547D\u540Dmybatis.configuration.map-underscore-to-camel-case=true
main.js
// The Vue build version to load with the `import` command// (runtime-only or standalone) has been set in webpack.base.conf with an alias.import Vue from ''vue''import App from ''./App''import router from ''./router''import axios from ''axios''import store from ''./store''import ElementUI from ''element-ui'';import ''element-ui/lib/theme-chalk/index.css'';Vue.use(ElementUI)import $ from ''jquery''axios.defaults.baseURL = ''http://139.222.222.122:9534'';Vue.prototype.HOST=''/user''Vue.config.productionTip = falseVue.prototype.$axios = axios/* eslint-disable no-new */new Vue({ el: ''#app'', router, store, components: { App }, template: ''<App/>''})
HelloWorld.vue
<template> <div> <div class="top_div"></div> <div style="background: rgb(255, 255, 255); margin: -100px auto auto; border: 1px solid rgb(231, 231, 231); border-image: none; width: 400px; height: 224px; text-align: center;"> <div style="width: 165px; height: 96px; position: absolute;"> <div class="tou"></div> <div class="initial_left_hand" id="left_hand"></div> <div class="initial_right_hand" id="right_hand"></div> </div> <p style="padding: 30px 0px 10px; position: relative;"><span class="u_logo"></span> <input id="loginName" class="ipt" type="text" v-model="userName" placeholder="请输入用户名" value=""> </p> <p style="position: relative;"><span class="p_logo"></span> <input class="ipt" id="password" type="password" v-model="password" placeholder="请输入密码" value=""> </p> <div id="errorText" style="height: 20px;margin-top:10px"> <p style="color: red;display: none">用户名密码错误请从新输入</p> </div> <div style="height: 50px; line-height: 50px; margin-top: 30px; border-top-color: rgb(231, 231, 231); border-top-width: 1px; border-top-style: solid;"> <!-- <p><span><a--> <!-- href="#">忘记密码?</a></span>--> <router-link to=''/Register''> <span style="float: left;margin-left: 10px;font-size: 14px;">没有账号?现在注册</span> </router-link>
<span style="float: right;"> <a id="loginBtn" @click="login()">登录</a> </span></div> </div> </div></template><script>export default { name: ''HelloWorld'', data () { return { userName: "", password:"" } }, created(){ }, methods:{ login: function(){ let fd = new FormData(); fd.append("username",this.userName); fd.append("pwd",this.password);
let config = { headers: { ''Content-Type'': ''application/x-www-form-urlencoded'' } } this.$axios.post("active", fd,config).then( res => { if(res.data.msg === "操作成功"){ console.log(res.data.data.username) this.$store.dispatch("login",res.data.data) this.$router.push({path:''/success''}) }else{ alert("用户名或密码错误") } }).catch( res => { alert("网络错误") //alert(res.data.msg) }) }
}}</script><!-- Add "scoped" attribute to limit CSS to this component only --><style scoped> body{ background: #ebebeb; font-family: "Helvetica Neue","Hiragino Sans GB","Microsoft YaHei","\9ED1\4F53",Arial,sans-serif; color: #222; font-size: 12px; } *{padding: 0px;margin: 0px;} .top_div{ background: #008ead; width: 100%; height: 240px; } .ipt{ border: 1px solid #d3d3d3; padding: 10px 10px; width: 290px; border-radius: 4px; padding-left: 35px; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075); box-shadow: inset 0 1px 1px rgba(0,0,0,.075); -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s } .ipt:focus{ border-color: #66afe9; outline: none; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6); box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6) } .u_logo{ background: url("../assets/images/username.png") no-repeat; padding: 10px 10px; position: absolute; top: 43px; left: 40px;
} .p_logo{ background: url("../assets/images/password.png") no-repeat; padding: 10px 10px; position: absolute; top: 12px; left: 40px; } a{ text-decoration: none; } .tou{ background: url("../assets/images/tou.png") no-repeat; width: 97px; height: 92px; position: absolute; top: -87px; left: 140px; } .left_hand{ background: url("../assets/images/left_hand.png") no-repeat; width: 32px; height: 37px; position: absolute; top: -38px; left: 150px; } .right_hand{ background: url("../assets/images/right_hand.png") no-repeat; width: 32px; height: 37px; position: absolute; top: -38px; right: -64px; } .initial_left_hand{ background: url("../assets/images/hand.png") no-repeat; width: 30px; height: 20px; position: absolute; top: -12px; left: 100px; } .initial_right_hand{ background: url("../assets/images/hand.png") no-repeat; width: 30px; height: 20px; position: absolute; top: -12px; right: -112px; } .left_handing{ background: url("../assets/images/left-handing.png") no-repeat; width: 30px; height: 20px; position: absolute; top: -24px; left: 139px; } .right_handinging{ background: url("../assets/images/right_handing.png") no-repeat; width: 30px; height: 20px; position: absolute; top: -21px; left: 210px; } #loginBtn { margin-right: 30px; background: rgb(0, 142, 173); padding: 7px 10px; border-radius: 4px; border: 1px solid rgb(26, 117, 152); border-image: none; color: rgb(255, 255, 255); font-weight: bold; cursor: pointer; }</style>
Register.vue
<template> <div> <div class="top_div"></div> <div style="background: rgb(255, 255, 255); margin: -100px auto auto; border: 1px solid rgb(231, 231, 231); border-image: none; width: 400px; height: 224px; text-align: center;"> <div style="width: 165px; height: 96px; position: absolute;"> <div class="tou"></div> <div class="initial_left_hand" id="left_hand"></div> <div class="initial_right_hand" id="right_hand"></div> </div> <p style="padding: 30px 0px 10px; position: relative;"><span class="u_logo"></span> <input id="loginName" class="ipt" type="text" v-model="userName" placeholder="请输入用户名" value=""> </p> <p style="position: relative;"><span class="p_logo"></span> <input class="ipt" id="password" type="password" v-model="password" placeholder="请输入密码" value=""> </p> <p style="position: relative;margin-top: 10px;"><span class="p_logo"></span> <input class="ipt" id="password2" type="password" v-model="password2" placeholder="请输入密码" value=""> </p> <p style="position: relative;margin-top: 10px;"><span class="p_logo"></span> <input class="ipt" id="email" type="email" v-model="email" placeholder="请输入邮箱" value=""> </p> <div id="errorText" style="height: 20px;margin-top:10px"> <p style="color: red;display: none">用户名密码错误请从新输入</p> </div> <div style="height: 50px; line-height: 50px; margin-top: 30px; border-top-color: rgb(231, 231, 231); border-top-width: 1px; border-top-style: solid;"> <!-- <p><span><a--> <!-- href="#">忘记密码?</a></span>--> <router-link to=''/''> <span style="float: left;margin-left: 10px;font-size: 14px;">已有账号,现在登录</span> </router-link>
<span style="float: right;"> <a id="loginBtn" @click="register()">注册</a> </span></div>
</div> </div></template><script>export default { name: ''Register'', data () { return { userName: "", password:"", password2:"", email:"" } }, created(){ }, methods:{ register: function(){ var reg = new RegExp("^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$"); //正则表达式 let fd = new FormData(); fd.append("username",this.userName); fd.append("pwd",this.password); fd.append("email",this.email); let config = { headers: { ''Content-Type'': ''application/x-www-form-urlencoded'' } } var flag = true; //判空 if(this.userName == "" || this.password == "" || this.email == ""){ alert("不能为空") flag = false; return; } if(!reg.test(this.email)){ alert("请填写正确的邮箱格式") flag = false; return; } if(this.password === this.password2 && flag){ this.$axios.post("/regist", fd).then( res => { alert(res.data.msg) }).catch( res => { alert(res.data.msg) }) }else{ alert("两次输入的密码不同")      }    } }}</script>
<!-- Add "scoped" attribute to limit CSS to this component only --><style scoped> body{ background: #ebebeb; font-family: "Helvetica Neue","Hiragino Sans GB","Microsoft YaHei","\9ED1\4F53",Arial,sans-serif; color: #222; font-size: 12px; } *{padding: 0px;margin: 0px;} .top_div{ background: #008ead; width: 100%; height: 240px; } .ipt{ border: 1px solid #d3d3d3; padding: 10px 10px; width: 290px; border-radius: 4px; padding-left: 35px; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075); box-shadow: inset 0 1px 1px rgba(0,0,0,.075); -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s } .ipt:focus{ border-color: #66afe9; outline: 0; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6); box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6) } .u_logo{ background: url("../assets/images/username.png") no-repeat; padding: 10px 10px; position: absolute; top: 43px; left: 40px;
} .p_logo{ background: url("../assets/images/password.png") no-repeat; padding: 10px 10px; position: absolute; top: 12px; left: 40px; } a{ text-decoration: none; } .tou{ background: url("../assets/images/tou.png") no-repeat; width: 97px; height: 92px; position: absolute; top: -87px; left: 140px; } .left_hand{ background: url("../assets/images/left_hand.png") no-repeat; width: 32px; height: 37px; position: absolute; top: -38px; left: 150px; } .right_hand{ background: url("../assets/images/right_hand.png") no-repeat; width: 32px; height: 37px; position: absolute; top: -38px; right: -64px; } .initial_left_hand{ background: url("../assets/images/hand.png") no-repeat; width: 30px; height: 20px; position: absolute; top: -12px; left: 100px; } .initial_right_hand{ background: url("../assets/images/hand.png") no-repeat; width: 30px; height: 20px; position: absolute; top: -12px; right: -112px; } .left_handing{ background: url("../assets/images/left-handing.png") no-repeat; width: 30px; height: 20px; position: absolute; top: -24px; left: 139px; } .right_handinging{ background: url("../assets/images/right_handing.png") no-repeat; width: 30px; height: 20px; position: absolute; top: -21px; left: 210px; } #loginBtn { margin-right: 30px; background: rgb(0, 142, 173); padding: 7px 10px; border-radius: 4px; border: 1px solid rgb(26, 117, 152); border-image: none; color: rgb(255, 255, 255); font-weight: bold; cursor: pointer; }</style>
Success.vue
<template> <div class="row"> <div class="col"> <div class="card"> <div class="card-header bg-light">用户管理</div> <div class="card-body"> <div class="table-responsive"> <div style="border-bottom:1px solid #d9d9d9;padding: 20px;"> <el-row > <el-col :span="4"> <img ref="authorImages" @click="avatarClick" v-if="uploadImg" :src="''http://139.159.147.237:9534/image/''+uploadImg" style="width:120px;height: 120px;border-radius: 100px;cursor: pointer" /> <div @click="avatarClick" v-else class="border border-primary rounded fm" style="width:120px;height: 120px;border-radius: 100px;line-height: 120px;text-align: center;cursor: pointer;background:#e9e9e9;"> <span style="font-size: 22px;color: #c7c7c7;">+</span> </div> </el-col>
<el-col :span="2"> <p style="line-height: 40px;">用户名:</p> </el-col> <el-col :span="4"> <el-input v-model="username" placeholder="请输入内容"></el-input> </el-col> <el-col :span="2"> <p style="line-height: 40px;">密码:</p> </el-col> <el-col :span="4"> <el-input v-model="pwd" placeholder="请输入内容"></el-input> </el-col> <el-col :span="2"> <p style="line-height: 40px;">邮箱:</p> </el-col> <el-col :span="4"> <el-input v-model="email" placeholder="请输入内容"></el-input> </el-col> </el-row>
<el-button type="primary" size="small" @click="onSubmit" style="margin: 10px 0">保存</el-button> <input type="file" ref="authorImg" style="display: none;" @change="getFile"></input> </div> </div> </div> </div> </div> </div></template><script> export default { data() { return { username: "", pwd: "", email: "", uploadImg: "" }; }, created() { console.log(this.$store.state.user) this.username = this.$store.state.user.username; this.pwd = this.$store.state.user.pwd; this.email = this.$store.state.user.email; this.uploadImg = this.$store.state.user.imagepath;
}, methods: { onSubmit: function(){ var reg = new RegExp("^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$"); //正则表达式 if(!reg.test(this.email)){ alert("请填写正确的邮箱格式") return; } let fd = new FormData(); fd.append("username",this.username); fd.append("pwd",this.pwd); fd.append("email",this.email); fd.append("imagepath",this.uploadImg); let config = { headers: { ''Content-Type'': ''application/x-www-form-urlencoded'' } } console.log(this.uploadImg) this.$axios.post("updateUser", fd,config).then( res => { console.log(res) if(res.data.message === "处理成功"){ alert("操作成功") this.$router.push({path:''/''}) }else{ alert("操作失败") } }).catch( res => { alert("网络错误") //alert(res.data.msg) }) }, avatarClick: function(){ this.$refs.authorImg.click(); }, getFile(){ let formData = new FormData(); formData.append("file", this.$refs.authorImg.files[0]); this.$axios.post("uploadImages",formData).then(res => { if(res.data.message === "处理成功"){ this.uploadImg = res.data.data; this.uploadImg = this.uploadImg.substr(11,this.uploadImg.length); alert("上传成功") }else{ alert("上传失败"); } }); } } };</script><style> .modal-title{ margin:10px 0; } .alert-text{ margin:10px 0; font-size:18px; } .modal-header:before{ content:none; } .modal-header:after{ content:none; }</style>

简表sql

CREATE TABLE T_USER (USERNAME VARCHAR(8),PWD varchar(30),ZT INT(1),CREATEDATE datetime,EMAIL VARCHAR(30),CODE VARCHAR (30),IMAGEPATH  VARCHAR(100));

--完--


如果你觉得这个案例以及我们的分享思路不错,对你有帮助,请分享给身边更多需要学习的朋友。别忘了《留言+点在看》给作者一个鼓励哦!


推荐案例

1、springboot+mybatis+vue前后端分离实现用户登陆注册功能

2、SpringBoot+Vue前后分离实现邮件发送功能

3、SpringBoot+Spring Data JPA+Vue前后端分离实现分页功能

4、SpringBoot+Spring Data JPA+Vue前后端分离实现Excel导出功能

5、Spring Boot + Vue前后端分离实现图片上传功能

6、springboot+jpa+tymeleaf实现分页功能

7、springboot+jpa+thymeleaf实现信息修改功能

8、SpringBoot+vue开发微信小程序留言功能

9、SpringBoot实现生成带参数的小程序二维码功能

10、springboot+jpa+thymeleaf实现信息增删改查功能

11、用java实现Graphics2D绘制验证码功能

12、Springboot+layui前后端分离实现word转pdf功能

13、用java将本地图片转base64格式, 再转图片!你用过这个功能?

14、springboot+layui+thymelefe实现用户批量添加功能

15、springboot+Tymeleaf实现用户密码MD5加盐解密登录

16、springboot+vue实现用户注册后必须通过邮箱激活才能登录激活才能登录

温暖提示

为了方便大家更好的学习,本公众号经常分享一些完整的单个功能案例代码给大家去练习,如果本公众号没有你要学习的功能案例,你可以联系小编(微信:xxf960513)提供你的小需求给我,我安排我们这边的开发团队免费帮你完成你的案例。
注意:只能提单个功能的需求不能要求功能太多,比如要求用什么技术,有几个页面,页面要求怎么样?


本文分享自微信公众号 - web项目开发(javawebkaifa)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

31. springboot+springcloud+vue b2b2c 商城之使用Turbine实现集群监控

31. springboot+springcloud+vue b2b2c 商城之使用Turbine实现集群监控

前面我们实现了对单个服务实例的监控,当然在实际应用中,单个实例的监控数据没有多大的价值,我们更需要的是一个集群系统的监控信息,这时我们就需要 Turbine。

Turbine 是用来监控集群的,通过它来汇集监控信息,并将聚合后的信息提供给 Hystrix Dashboard 来集中展示和监控。

Turbine 使用
Turbine 是聚合服务器发送事件流数据的一个工具。Hystrix 只能监控单个节点,然后通过 dashboard 进行展示。实际生产中都为集群,这个时候我们可以通过 Turbine 来监控集群下 Hystrix 的 metrics 情况,通过 Eureka 来发现 Hystrix 服务。
本节在介绍 Turbine 的用法时就不再单独创建一个新项目了,在之前的 hystrix-dashboard-demo 中进行修改来支持 Turbine 即可。

首先增加 Turbine 的依赖,代码如下所示。

<dependency>

<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>

</dependency>
在启动类上增加 @EnableTurbine 和 @EnableDiscoveryClient。在属性文件中配置如下内容:

eureka.client.serviceUrl.defaultZone=http://zhangsan:123456@localh...
turbine.appConfig=hystrix-feign-demo
turbine.aggregator.clusterConfig=default
turbine.clusterNameExpression=new String("default")

其中:

turbine.appConfig:配置需要聚合的服务名称。
turbine.aggregator.clusterConfig:Turbine 需要聚合的集群名称。
turbine.clusterNameExpression:集群名表达式。
这里用默认的集群名称 default。

重启服务,就可以使用 http://localhost:9011/turbine... 来访问集群的监控数据了。Turbine 会通过在 Eureka 中查找服务的 homePageUrl 加上 hystrix.stream 来获取其他服务的监控数据,并将其汇总显示。

context-path 导致监控失败
如果被监控的服务中设置了 context-path,则会导致 Turbine 无法获取监控数据,如图 1 所示。

这个时候需要在 Turbine 中指定 turbine.instanceUrlSuffix 来解决这个问题:

turbine.instanceUrlSuffix=/sub/hystrix.stream
sub 用于监控服务的 context-path。上面这种方式是全局配置,会有一个问题,就是一般我们在使用中会用一个集群去监控多个服务,如果每个服务的 context-path 都不一样,这个时候有一些就会出问题,那么就需要对每个服务做一个集群,然后配置集群对应的 context-path:

turbine.instanceUrlSuffix. 集群名称 =/sub/hystrix.stream
推荐布式微服务商城

Koa & Mongoose & Vue实现前后端分离--11更新用户头像

Koa & Mongoose & Vue实现前后端分离--11更新用户头像

上节回顾

  • 更新用户文本数据

工作内容

  • 更新用户数据
  • 图片上传 & 存储 & 静态化访问

准备工作

  • npm i -S koa-static // 先切换到/server目录下

业务逻辑

服务端文件路由配置

服务端拦截路由请求:

// 新建文件:server/router/assets.js
const Router =  require(''@koa/router'');

const controls =  require(''../control/assets'');

const routerUtils =  require(''../utils/router'');

const { upload } = controls;

const router =  new Router({

    prefix:  ''/assets''

});

const routes = [

    {

    path:  ''/:category/:id'',

    method:  ''POST'',

    handle: upload

    }
] 

routerUtils.register.call(router, routes);

module.exports = router;
// 新建文件:server/control/assets.js
async  function  upload  (ctx, next) {
    console.log(''--------upload======='')
}

module.exports = {
    upload,
}

koa-body支持

// 更新文件:server/app.js
...
const path =  require(''path'');
...
app.use(bodyParser({
    multipart: true, //支持文件数据
    formidable: {
        uploadDir: path.resolve(__dirname, ''./public/temp''), // 图片存储位置
        keepExtensions: true
    }
}));
...
  • multipart: true支持文件数据,这里以key:value的形式获取。
  • formidable.uploadDir文件上传存储位置,若不设置,默认存储到计算机用户目录的缓存位置,最好设置一个可控位置。
  • keepExtensions:true是否保有后缀名,默认不存。

Postman测试

  • 前期,登录时,已经设置全局变量token
  • Body --> form-data --> File --> Select Files

upload.gif

请求结果:没有任何返回。
vs code的调试控制台反馈:
error.png
设置存储文件的路径不存在。

保存文件

存储路径不存在,需要检测文件目录是否存在,若不存在,则新建。

// 新建文件:server/utils/dir.js
const path =  require(''path'');
const fs =  require(''fs''); 

function  checkDirExist(dirname) {
    if (fs.existsSync(dirname)) {
        return true;
    } else {
        if (checkDirExist(path.dirname(dirname))) {
            fs.mkdirSync(dirname); //递归
            return true;
        }
    }
}

module.exports = {
    checkDirExist,
}

方式一、通过koa-body配置

// 更新文件:
...
const { checkDirExist } =  require(''./utils/dir'');
const fileTempDir = path.resolve(__dirname, ''./public/temp'');
...
app.use(bodyParser({
    multipart: true,
    formidable: {
        uploadDir: fileTempDir,
        keepExtensions: true,
        onFileBegin(key, file) { // 利用钩子函数
            checkDirExist(fileTempDir);
            //file.path= path.resolve(fileTempDir, file.name) // 文件改名
        }
    }
}));
  • 利用koa-body配置onFileBegin,可以在处理文件之前,进行一些操作,如:测试目录是否存在、改名、改存储路径等。

测试结果:
filename

方式二、fs模块读写文件

那,为什么还要第二种方式呢?
利用koa-body配置onFileBegin改名,只能获取到file数据本身的数据信息,而无法获取ctx的上下文信息(如,这里准备根据请求参数创建目录存储文件)。

// 更新文件:server/control/assets.js
const fs =  require(''fs'');
const path =  require(''path'');
const { checkDirExist } =  require(''../utils/dir'');

async  function  upload  (ctx, next) {
    const file = Object.values(ctx.request.files)[0];
    const { category, id } = ctx.params;
    const filePath = file.path;
    // 最终要保存到的文件夹路径
    const dir = path.join(__dirname,`../public/${category}/${id}/`);
   try {
        // 检查文件夹是否存在——>如果不存在,则新建文件夹
        checkDirExist(dir);
        const reader = fs.createReadStream(filePath);
        const writer = fs.createWriteStream(path.resolve(dir, file.name));
        reader.pipe(writer);
        // 删除缓存文件
        fs.unlinkSync(filePath)
    } catch (err) {

    }
}

module.exports = {
    upload
}
  • 这里,根据上传路由的参数,将文件存到用户ID创建的avatar目录下。

Postman测试结果:
avatar

访问文件

这时候访问文件:
error
所以,需要将public目录添加到身份认证的unless白名单中:

// 更新文件:server/app.js
...
custom:  function(ctx) {
    const { method, path, query } = ctx;
    if(path ===  ''/''){
        return true;
    }
    if(/^\\/public/.test(path)) { // public目录
        return true;
    }
    if(path ===  ''/users'' && query.action) {
        return true;
    }
    return false;
}
...

继续访问:
image.png

这是因为默认会请求动态资源,而图片数据静态资源

// 更新文件:
...
const koaStatic =  require(''koa-static'');
...
// 中间件:指定静态资源路径 vs. 使其与动态资源分离
app.use(koaStatic(path.join(__dirname, ''public/'')))

继续访问,报错如上:
image.png

这是因为访问路径错了:访问路径不用带/public
dir.gif

若怀疑,不加koa-static,仅修改路径即可访问的可以自行试试。

服务端更新用户头像逻辑

上述内容中,修改了upload的业务逻辑,仅涉及到将文件保存到指定路径下,却没有去更新用户头像信息,并且,服务端没有返回数据。

// 更新文件:server/control/assets.js
const fs =  require(''fs'');
const path =  require(''path'');
const userModel =  require(''../model/user'');
const { checkDirExist } =  require(''../utils/dir'');

async  function  upload  (ctx, next) {
const file = Object.values(ctx.request.files)[0];
const { category, id } = ctx.params;
// 用户头像远程地址
const remotePath =  `${ctx.origin}/${category}/${id}/${file.name}`;
const filePath = file.path;
const dir = path.join(__dirname,`../public/${category}/${id}/`);
try {
    checkDirExist(dir);
    const reader = fs.createReadStream(filePath);
    const writer = fs.createWriteStream(path.resolve(dir, file.name));
    reader.pipe(writer);
    try {
    // 更新用户头像信息
        await userModel.updateOne(
        {
            _id: id
        },
        {
            avatar: remotePath
        }
    ).exec();
    } catch (err) {
        ctx.body = {
            code:  ''404'',
            data: null,
            msg:  ''上传失败''
        };
        return;
    }
    fs.unlinkSync(filePath)
    ctx.body = {
        code:  ''200'',
        data: {
            filePath: remotePath
        },
        msg:  ''上传成功''
    }
    } catch (err) {
        ctx.body = {
            code:  ''404'',
            data: null,
            msg:  ''上传失败''
        }
    } 
} 

module.exports = {
    upload
}
  • 设置远程访问地址${ctx.origin}/${category}/${id}/${file.name},并将该地址更新到用户信息中。

这里将/server/public目录删除,重新测试:
sucess.gif

前端页面头像逻辑

//更新文件:
...
methods: {
...
handleAvatarSuccess  (res,  file)  {
    this.imageUrl  =  URL.createObjectURL(file.raw)
},

beforeAvatarUpload  (file)  {
    const  isJPG  = /^image\//.test(file.type)
    const  isLt2M  =  file.size  /  1024  /  1024  /  10  <  2
    if (!isJPG) {
    this.$message.error(''上传头像图片只能是 JPG 格式!'')
    }
    if (!isLt2M) {
    this.$message.error(''上传头像图片大小不能超过 20MB!'')
    }
    return  isJPG  &&  isLt2M
},
...
data () {
  return {
      uploadForm:  {
        action:  `//localhost:3000/assets/avatars/${this.$store.state.loginer.id}`,
        headers:  {
            ''Authorization'':  `Bearer ${localStorage.getItem(''token'')}`
        },
        multiple:  false,
        ''show-file-list'':  false,
        ''on-success'':  this.handleAvatarSuccess,
        ''before-upload'':  this.beforeAvatarUpload
      },
  ...
async  created  ()  {
    const  res  =  await  http.get(`/users/${this.userId}`)
    if (res.code  ===  ''200'') {
        this.loginer =  res.data
        this.dialogForm.form =  {...res.data}
        this.imageUrl =  res.data.avatar //初始化时,赋值
    }  else  {
        this.$message({
            type:  ''error'',
            message:  ''获取用户信息失败''
        })
    }
}
  • 初始化时,给头像赋值;
  • 通过uploadForm设置上传配置
  • on-success上传成功时,将图片地址覆盖原有值;
  • before-upload上传之前,校验文件类型和大小;

效果展示:
fronEnd.gif

参考文档

koa-static
koa-body上传文件相关配置

spring boot+vue实现爬取各大平台每日热榜数据功能

spring boot+vue实现爬取各大平台每日热榜数据功能

爬取各大热门APP

案例功能效果图
爬去数据的平台页面

这个案例能爬取的平台太多了,我没有全部截图出来,想看的你们自己下载源码自己跑起来!
爬取的热榜数据效果图

环境介绍
前端:vue+h5
后端:springboot+webMagic
jdk:1.8及以上
数据库:mysql

完整源码获取方式
源码获取方式:点击这里,暗号博客园!

核心代码介绍
pom.

<!-- https://mvnrepository.com/artifact/us.codecraft/webmagic-core --> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-core</artifactId> <version>0.7.3</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> <!-- https://mvnrepository.com/artifact/us.codecraft/webmagic-extension --> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-extension</artifactId> <version>0.7.3</version> </dependency> <!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok 代码省略工具--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.1</version> </dependency>

application.yml

server: port: 9004spring: jackson: serialization: write-dates-as-timestamps: true datasource: driverClassName: com.mysql.cj.jdbc.Driver url: jdbc:mysql://feimeidehuoji:3306/feimeidehuoji?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC username: feimeidehuoji password: feimeidehuoji jpa: database: MySQL show-sql: true hibernate: ddl-auto: update database-platform: org.hibernate.dialect.MySQL5InnoDBDialectspiderUrl: https://tophub.todayproxyUrl: 61.160.210.234proxyPort: 808

NodeController.java

package cn.cesi.webMagic.webMagic;import cn.cesi.webMagic.pieline.SpringPieline;import cn.cesi.webMagic.pojo.Node;import cn.cesi.webMagic.service.NodeService;import cn.cesi.webMagic.util.Result;import cn.cesi.webMagic.util.StatusCode;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiParam;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.domain.Page;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import us.codecraft.webmagic.Spider;import us.codecraft.webmagic.downloader.HttpClientDownloader;import us.codecraft.webmagic.proxy.Proxy;import us.codecraft.webmagic.proxy.SimpleProxyProvider;import us.codecraft.webmagic.scheduler.BloomFilterDuplicateRemover;import us.codecraft.webmagic.scheduler.QueueScheduler;import javax.annotation.Resource;import java.util.List;import java.util.Map;@RestController@CrossOrigin@RequestMapping("/node")@Api(value = "获取数据接口",tags={"用户登录接口"})public class NodeController { @Value("${spiderUrl}") private String url; @Value("${proxyUrl}") private String proxyUrl; @Value("${proxyPort}") private Integer proxyPort; @Resource NodeService nodeService; @Autowired SpringPieline springPieline; @RequestMapping("") @ApiOperation(value = "查询数据接口") public Result getData( @ApiParam(value = "分类名称", required = false) String typeName ,@ApiParam(value = "分类名称", required = false) String secondTitle ,@ApiParam(value = "当前页", required = false)Integer page ,@ApiParam(value = "每页数据条数", required = false)Integer size){ Page<Node> nodes = nodeService.searchData(typeName, secondTitle,page, size); Result result = new Result(); result.setFlag(true); result.setCode(StatusCode.OK); result.setMsg("查询成功!"); result.setData(nodes); return result; } @RequestMapping("/getType") @ApiOperation(value = "查询全部分类列表") public Result getData(){ List<Map<String,String>> list = nodeService.findType(); Result result = new Result(); result.setFlag(true); result.setCode(StatusCode.OK); result.setMsg("查询成功!"); result.setData(list); return result; } @Scheduled(fixedDelay = 480000) //1000*60*8 任务执行完成后10分钟继续执行 public void tasks(){ System.out.println("定时任务开始——————————————————————————————————"); //设置代理服务器 HttpClientDownloader httpClientDownloader = new HttpClientDownloader(); httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy(proxyUrl,proxyPort))); Spider.create(new WebProcess()) .addUrl(url) .setDownloader(httpClientDownloader) .thread(2) //线程(程序爬取速度) .addPipeline(springPieline) //指定pieline接口 .setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(10000*10))) .run(); System.out.println("定时任务结束——————————————————————————————————"); }}

WebProcess.java

package cn.cesi.webMagic.webMagic;import cn.cesi.webMagic.pieline.SpringPieline;import cn.cesi.webMagic.util.NodeEntity;import org.jsoup.Jsoup;import org.jsoup.nodes.Document;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import us.codecraft.webmagic.Page;import us.codecraft.webmagic.Site;import us.codecraft.webmagic.Spider;import us.codecraft.webmagic.downloader.HttpClientDownloader;import us.codecraft.webmagic.processor.PageProcessor;import us.codecraft.webmagic.proxy.Proxy;import us.codecraft.webmagic.proxy.SimpleProxyProvider;import us.codecraft.webmagic.scheduler.BloomFilterDuplicateRemover;import us.codecraft.webmagic.scheduler.QueueScheduler;import us.codecraft.webmagic.selector.Selectable;import org.jsoup.select.Elements;import java.util.*;@Componentpublic class WebProcess implements PageProcessor { @Override public void process(Page page) { System.out.println(page.getHtml()); //page页面对象,getHtml()获取页面的html ,css()选择器 div#Sortable 获取id为Sortable的div元素 nodes()转为集合 List<Selectable> list = page.getHtml().css("div.bc div#Sortable div.cc-cd div").nodes(); List<NodeEntity> nodes = new ArrayList<>(); for(Selectable selectable : list){ //regex 正则表达式// String name = Jsoup.parse(selectable.css("div.cc-cd-ih div a div span").regex(".*微博.*").all().toString()).text(); //标题 //Jsoup.parse解析html为dom元素(对象)语法同js语法 text()为js语法不多解释 //获取title大标题 String s = selectable.css("div.cc-cd-ih div a div span").toString(); String title = ""; if(s != null){ title = Jsoup.parse(s).text(); } //获取logo String logo = selectable.css("div.cc-cd-ih div a div img").toString(); String logoSrc = ""; if(logo != null){ Document document = Jsoup.parse(logo); Elements imgTags = document.select("img[src]"); logoSrc = imgTags.attr("src"); } //获取第二层小标题的集合 List<Selectable> list2 = selectable.css("div.cc-cd-cb div a").nodes(); List<Map<String,Strin.........

Springboot Vue Login(从零开始实现Springboot+Vue登录)

Springboot Vue Login(从零开始实现Springboot+Vue登录)

小Hub领读:

一个完整的Spirngboot+vue实现登录的小例子,我之前在vueblog中也搞过,哈哈,再来回顾一下!


作者:Eli Shaw

https://blog.csdn.net/xiaojin...

一、简述

最近学习使用 Vue 实现前端后端分离,在 Github 上有一个很好的开源项目:mall,正所谓百看不如一练,自己动手实现了一个 Springboot+Vue 的登录操作,在此记录一下踩过的坑。

文章最后补充两端的 GitHub 代码,之所以放在最后,是因为文章写的很细致了,动手操作一下会更有帮忙,如果有很大出入可以比对原码,找找问题。

二、开发工具

VSCode

IDEA

Vue 的安装就不说了,有很多文章,但是 Springboot+Vue 整合的完整文章相对较少,所以我主要记录一下这两端整合时的内容。

(Vue 安装后就会有 npm 或 cnpm,相应的介绍也不说了,Vue 官网可查看)

一、打开 cmd 创建 Vue 项目,并添加 Vue 依赖的框架:

1\. 创建 Vue 项目 (进入自己想创建的文件夹位置,我放在 D:\VSCodeWorkSpace),创建语句 vue create vue-spring-login-summed,方向键选择创建方式,我选择的默认

2\. 进入到创建的 Vue 项目目录,添加依赖框架:

cd vue-spring-login-summed (进入到项目根目录)
vue add element (添加 element,一个 element 风格的 UI 框架)
npm install axios (安装 axios,用于网络请求)
npm install vuex --save(安装 Vuex,用于管理状态)
npm install vue-router (安装 路由,用于实现两个 Vue 页面的跳转)

以上命令截图如下:

1) 添加 Element

2) 添加 axios

3) 添加 Vuex

4) 添加 路由

到此相关依赖的架包添加完毕,输入 code . 打开 VSCode

二、添加目录结构

在 VSCode 下看到 Vue 整体项目结构如下

现在需要创建相应功能的目录结构,进行分层开发,需要在 src 目录下创建下面几个目录

api (网络请求接口包)
router (路由配置包)
store (Vuex 状态管理包)
utils (工具包)
views (vue 视图包,存放所有 vue 代码,可根据功能模块进行相应分包)

创建后的目录结构如下

三、运行项目

现在可以运行项目了,在 VSCode 菜单栏依次选择:终端 —— 运行任务...

这里使用的是 serve 模式,即开发模式运行的项目

在浏览器输入:http://localhost:8080/

这是 Vue 默认的页面,代表项目创建成功了,在进行代码开发前,先贴上项目整体结构,防止不知道在哪创建

四、View 层代码编写

编写三个 vue 文件:login.vue(登录页面)、success.vue(登录成功页面)、error.vue(登录失败页面)

1.login.vue 

代码如下 (比较懒,直接从 mall 扒下来的代码,去掉了一些功能)

<template>
  <div>
    <el-card>
      <el-form
        autocomplete="on"
        :model="loginForm"
        ref="loginForm"
        label-position="left"
      >
        <div>
          <svg-icon icon-></svg-icon>
        </div>
        <h2>mall-admin-web</h2>
        <el-form-item prop="username">
          <el-input
            
            type="text"
            v-model="loginForm.username"
            autocomplete="on"
            placeholder="请输入用户名"
          >
            <span slot="prefix">
              <svg-icon icon-></svg-icon>
            </span>
          </el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input
            
            :type="pwdType"
            @keyup.enter.native="handleLogin"
            v-model="loginForm.password"
            autocomplete="on"
            placeholder="请输入密码"
          >
            <span slot="prefix">
              <svg-icon icon-></svg-icon>
            </span>
            <span slot="suffix" @click="showPwd">
              <svg-icon icon-></svg-icon>
            </span>
          </el-input>
        </el-form-item>
        <el-form-item>
          <el-button
           
            type="primary"
            :loading="loading"
            @click.native.prevent="handleLogin"
          >登录</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>
 
<script>
export default {
  name: "login",
  data() {
    return {
      loginForm: {
        username: "admin",
        password: "123456"
      },
      loading: false,
      pwdType: "password",
    };
  },
  methods: {
    showPwd() {
      if (this.pwdType === "password") {
        this.pwdType = "";
      } else {
        this.pwdType = "password";
      }
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          this.$store
            .dispatch("Login", this.loginForm)
            .then(response => {
              this.loading = false;
              let code = response.data.code;
              if (code == 200) {
                this.$router.push({
                  path: "/success",
                  query: { data: response.data.data }
                });
              } else {
                this.$router.push({
                  path: "/error",
                  query: { message: response.data.message }
                });
              }
            })
            .catch(() => {
              this.loading = false;
            });
        } else {
          // eslint-disable-next-line no-console
          console.log("参数验证不合法!");
          return false;
        }
      });
    }
  }
};
</script>
 
<style scoped>
.login-form-layout {
  position: absolute;
  left: 0;
  right: 0;
  width: 360px;
  margin: 140px auto;
  border-top: 10px solid #409eff;
}
 
.login-title {
  text-align: center;
}
 
.login-center-layout {
  background: #409eff;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  margin-top: 200px;
}
</style>

2.success.vue

<template>
  <div>
    <h1>Welcome!{{msg}}</h1>
  </div>
</template>
<script>
export default {
  data() {
    return {
      msg: this.$route.query.data
    };
  },
//   data() { //这种方式也可以
//     return {
//       msg: null
//     };
//   },
  // created() {
  //   this.msg = this.$route.query.data;
  // }
}
</script>

3.error.vue

<template>
  <div>
    <h1>登录错误:{{msg}}</h1>
  </div>
</template>
<script>
export default {
  // data() {
  //   return {
  //     msg: this.$route.query.data
  //   };
  // }, //使用这种方式也可以显示 msg
  data() {
    return {
      msg: null
    };
  },
  created() {
    this.msg = this.$route.query.message;
  }
};
</script>

五、路由

页面写好了,我们需要依次显示这三个页面,这里我们统一使用路由来管理显示页面,路由的官方文档见:vue 路由

本着先实践,后理解的码农学习方式。我们先使用路由显示三个页面后,再去理解 Vue 路由这个功能点。

1\. 创建路由配置文件

在刚才建立的 router 文件夹下创建一个 index.js 文件,内容如下

import Vue from ''vue'' //引入 Vue
import VueRouter from ''vue-router'' //引入 Vue 路由
 
Vue.use(VueRouter); //安装插件
 
export const constantRouterMap = \[
    //配置默认的路径,默认显示登录页
    { path: ''/'', component: () => import(''@/views/login'')},
 
    //配置登录成功页面,使用时需要使用 path 路径来实现跳转
    { path: ''/success'', component: () => import(''@/views/success'')},
 
    //配置登录失败页面,使用时需要使用 path 路径来实现跳转
    { path: ''/error'', component: () => import(''@/views/error''), hidden: true }
\]
 
export default new VueRouter({
    // mode: ''history'', //后端支持可开
    scrollBehavior: () => ({ y: 0 }),
    routes: constantRouterMap //指定路由列表
})

2\. 将路由添加到程序入口

路由配置文件写好,我们需要把他引入到 main.js 中,在项目的 src 目录根节点下,找到 main.js,添加内容如下:

import Vue from ''vue''
import App from ''./App.vue''
import ''./plugins/element.js''
import router from ''./router'' //引入路由配置
 
Vue.config.productionTip = false
 
new Vue({
  render: h => h(App),
  router, //使用路由配置
}).$mount(''#app'')

3\. 配置路由的出入口

现在路由已经完全引入到项目了,但是路由还需要一个出入口,这个出入口用来告诉路由将路由的内容显示在这里。上面 main.js 配置的第一个 vue 显示页面为 App.vue ,因此我们修改 App.vue 内容如下

<template>
  <div>
    <!-- 路由的出入口,路由的内容将被显示在这里 -->
    <router-view/>
  </div>
</template>
 
<script>
  export default {
    name: ''App''
  }
</script>

<router-view/> 就是显示路由的出入口。

现在保存 App.vue 文件后,当前项目会被重新装载运行,在刚才浏览的界面就会看到登录界面如下:

4\. 路由跳转

在 login.vue 中可以使用 this.$router.push({path: "路径"}) 来跳转到指定路径的路由组件中,下面是通过路由跳转到 error.vue 与 success.vue 的代码

this.$router.push({path: "/success"}); //跳转到成功页
或
this.$router.push({path: "/error"}); //跳转到失败页

六、使用 Vuex + Axios 方式进行网络请求

1.Axios

axios 是一个网络请求构架,官方推荐使用这种方式进行 http 的请求。

1) 在 utils 包下封装一个请求工具类 request.js

import axios from ''axios'' //引入 axios
import baseUrl from ''../api/baseUrl'' //使用环境变量 + 模式的方式定义基础URL
 
// 创建 axios 实例
const service = axios.create({
  baseURL: baseUrl, // api 的 base\_url
  timeout: 15000, // 请求超时时间
})
 
export default service

这里的 baseUrl 涉及 Vue CLI3 的环境变量与模式的概念,见:Vue 环境变量和模式 (设置通用 baseUrl)

2) 登录请求接口 API

在 api 文件夹下,创建一个登录 API 文件:login.js

import request from ''@/utils/request'' //引入封装好的 axios 请求
 
export function login(username, password) { //登录接口
  return request({ //使用封装好的 axios 进行网络请求
    url: ''/admin/login'',
    method: ''post'',
    data: { //提交的数据
      username,
      password
    }
  })
}

2\. 使用 Vuex 封装 axios

Vuex 是一个状态管理构架,官方文档:Vuex

1) 封装 Vuex 中的 module

在 store 文件夹下创建一个 modules 文件夹,然后在此文件夹下创建一个 user.js 文件

import { login } from ''@/api/login''//引入登录 api 接口
 
const user = {
  actions: {
    // 登录
    Login({ commit }, userInfo) { //定义 Login 方法,在组件中使用 this.$store.dispatch("Login") 调用
      const username = userInfo.username.trim()
      return new Promise((resolve, reject) => { //封装一个 Promise
        login(username, userInfo.password).then(response => { //使用 login 接口进行网络请求
          commit('''') //提交一个 mutation,通知状态改变
          resolve(response) //将结果封装进 Promise
        }).catch(error => {
          reject(error)
        })
      })
    },
  }
}
export default user

这里的代码值得解释一下:官方文档对应:Vuex actions

1\. 首先引入 login 接口,之后使用登录接口进行网络请求。

2\. 定义一个 名为 Login 的 action 方法,Vue 组件通过 this.$store.dispatch("Login") 调用

3.Promise,这个类很有意思,官方的解释是 “store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise”。这话的意思组件中的 dispatch 返回的仍是一个 Promise 类,因此推测 Promise 中的两个方法 resolve() 与 reject() 分别对应 dispatch 中的 then 与 catch。

2) 创建 Vuex

在 store 文件夹下创建一个 index.js 文件

import Vue from ''vue'' //引入 Vue
import Vuex from ''vuex'' //引入 Vuex
import user from ''./modules/user'' //引入 user module
 
Vue.use(Vuex)
 
const store = new Vuex.Store({
  modules: {
    user //使用 user.js 中的 action
  }
})
 
export default store

3) 将 Vuex 添加到 main.js 文件

修改之前的 main.js 文件如下:

import Vue from ''vue''
import App from ''./App.vue''
import ''./plugins/element.js''
import router from ''./router'' //引入路由配置
import store from ''./store'' //引入 Vuex 状态管理
 
Vue.config.productionTip = false
 
new Vue({
  render: h => h(App),
  router, //使用路由配置
  store //使用 Vuex 进行状态管理
}).$mount(''#app'')

重新运行项目,在 Chrome 浏览器中进入调试模式,点击登录按钮

可以看到有发送一个 8088 端口的请求,至此 Vue 端的所有代码已经完成。

\-------------------------------Springboot 开发 -------------------------------

项目创建就不提了,网上有很多,只要使用 Spring Assistant 创建就好。

整体目录结构如下

1\. 在 application.yml 修改端口号

不要和 Vue 在一个 8080 端口上:

server:
  port: 8088

2\. 解决跨域问题

这里有一个跨域问题,即 Vue 使用 8080 端口,要访问 8088 端口的服务器,会报错。错误信息如下:

Access to XMLHttpRequest at ''http://localhost:8088/admin/login'' from origin ''http://localhost:8080'' has been blocked by CORS policy: Response to preflight request doesn''t pass access control check: No''Access-Control-Allow-Origin'' header is present on the requested resource.

这个问题在 Vue 端或在 Springboot 端处理都可以,我在 Springboot 端处理的,写一个 CorsConfig 类内容如下,不要忘了 @Configuration 注解。

@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("\*"); // 1
        corsConfiguration.addAllowedHeader("\*"); // 2
        corsConfiguration.addAllowedMethod("\*"); // 3
        return corsConfiguration;
    }
 
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/\*\*", buildConfig()); // 4
        return new CorsFilter(source);
    }
}

3.IErrorCode 接口

Java 版本

public interface IErrorCode {
    long getCode();
    String getMessage();
}

Kotlin 版本

interface IErrorCode {
    fun getCode(): Long
    fun getMessage(): String
}

4.CommonResult 类

Java 版本

public class CommonResult<T> {
    private long code;
    private String message;
    private T data;
 
    protected CommonResult() {
    }
 
    protected CommonResult(long code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }
 
    /\*\*
     \* 成功返回结果
     \*
     \* @param data 获取的数据
     \*/
    public static <T> CommonResult<T> success(T data) {
        return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
    }
 
    /\*\*
     \* 成功返回结果
     \*
     \* @param data    获取的数据
     \* @param message 提示信息
     \*/
    public static <T> CommonResult<T> success(T data, String message) {
        return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
    }
 
    /\*\*
     \* 失败返回结果
     \*
     \* @param errorCode 错误码
     \*/
    public static <T> CommonResult<T> failed(IErrorCode errorCode) {
        return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
    }
 
    /\*\*
     \* 失败返回结果
     \*
     \* @param message 提示信息
     \*/
    public static <T> CommonResult<T> failed(String message) {
        return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
    }
 
    /\*\*
     \* 失败返回结果
     \*/
    public static <T> CommonResult<T> failed() {
        return failed(ResultCode.FAILED);
    }
 
    /\*\*
     \* 参数验证失败返回结果
     \*/
    public static <T> CommonResult<T> validateFailed() {
        return failed(ResultCode.VALIDATE\_FAILED);
    }
 
    /\*\*
     \* 参数验证失败返回结果
     \*
     \* @param message 提示信息
     \*/
    public static <T> CommonResult<T> validateFailed(String message) {
        return new CommonResult<T>(ResultCode.VALIDATE\_FAILED.getCode(), message, null);
    }
 
    /\*\*
     \* 未登录返回结果
     \*/
    public static <T> CommonResult<T> unauthorized(T data) {
        return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
    }
 
    /\*\*
     \* 未授权返回结果
     \*/
    public static <T> CommonResult<T> forbidden(T data) {
        return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
    }
 
    public long getCode() {
        return code;
    }
 
    public void setCode(long code) {
        this.code = code;
    }
 
    public String getMessage() {
        return message;
    }
 
    public void setMessage(String message) {
        this.message = message;
    }
 
    public T getData() {
        return data;
    }
 
    public void setData(T data) {
        this.data = data;
    }
}

Kotlin 版本

class CommonResult<T> {
    var code: Long = 0
    var message: String? = null
    var data: T? = null
 
    constructor(code: Long, message: String, data: T?) {
        this.code = code
        this.message = message
        this.data = data
    }
 
    companion object {
 
        /\*\*
         \* 成功返回结果
         \* @param data 获取的数据
         \*/
        fun <T> success(data: T): CommonResult<T> {
            return CommonResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data)
        }
 
        /\*\*
         \* 成功返回结果
         \* @param data 获取的数据
         \* @param  message 提示信息
         \*/
        fun <T> success(data: T, message: String): CommonResult<T> {
            return CommonResult(ResultCode.SUCCESS.getCode(), message, data)
        }
 
        /\*\*
         \* 失败返回结果
         \* @param errorCode 错误码
         \*/
        fun <T> failed(errorCode: IErrorCode): CommonResult<T> {
            return CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null)
        }
 
        /\*\*
         \* 失败返回结果
         \* @param message 提示信息
         \*/
        fun <T> failed(message: String): CommonResult<T> {
            return CommonResult<T>(ResultCode.FAILED.getCode(), message, null)
        }
 
        /\*\*
         \* 失败返回结果
         \*/
        fun failed(): CommonResult<Any> {
            return failed(ResultCode.FAILED)
        }
 
        /\*\*
         \* 参数验证失败返回结果
         \*/
        fun validateFailed(): CommonResult<Any> {
            return failed(ResultCode.VALIDATE\_FAILED)
        }
 
        /\*\*
         \* 参数验证失败返回结果
         \* @param message 提示信息
         \*/
        fun <T> validateFailed(message: String): CommonResult<T> {
            return CommonResult<T>(ResultCode.VALIDATE\_FAILED.getCode(), message, null)
        }
 
        /\*\*
         \* 未登录返回结果
         \*/
        fun <T> unauthorized(data: T): CommonResult<T> {
            return CommonResult(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data)
        }
 
        /\*\*
         \* 未授权返回结果
         \*/
        fun <T> forbidden(data: T): CommonResult<T> {
            return CommonResult(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data)
        }
    }
}

5.ResultCode 枚举

Java 版本

public enum ResultCode implements IErrorCode {
    SUCCESS(200, "操作成功"),
    FAILED(500, "操作失败"),
    VALIDATE\_FAILED(404, "参数检验失败"),
    UNAUTHORIZED(401, "暂未登录或token已经过期"),
    FORBIDDEN(403, "没有相关权限");
    private long code;
    private String message;
 
    private ResultCode(long code, String message) {
        this.code = code;
        this.message = message;
    }
 
    public long getCode() {
        return code;
    }
 
    public String getMessage() {
        return message;
    }
}

Kotlin 版本

enum class ResultCode(private val code: Long, private val message: String) : IErrorCode {
    SUCCESS(200, "操作成功"),
    FAILED(500, "操作失败"),
    VALIDATE\_FAILED(404, "参数检验失败"),
    UNAUTHORIZED(401, "暂未登录或token已经过期"),
    FORBIDDEN(403, "没有相关权限");
 
    override fun getCode(): Long {
        return code
    }
 
    override fun getMessage(): String {
        return message
    }
}

6.User 类

Java 版本

public class User {
 
    private int id;
    private String username;
    private String password;
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
}

Kotlin 版本

data class User(
        val id: Int,
        val username: String,
        val password: String)

7.LoginController 类

Java 版本

@RestController
public class LoginController {
 
    @RequestMapping(value = "/admin/login", method = RequestMethod.POST)
    public CommonResult login(@RequestBody User user) {
        if (user.getUsername().equals("admin") && user.getPassword().equals("123456"))
            return CommonResult.success("admin");
        else
            return CommonResult.validateFailed();
    }
}

Kotlin 版本

@RestController //此注解是 @ResponseBody 和 @Controller 的组合注解,可返回一个 JSON
class LoginController {
 
    @RequestMapping(value = \["/admin/login"\], method = \[RequestMethod.POST\])
    fun admin(@RequestBody user: User): CommonResult<\*> {
        return if (user.username == "admin" && user.password == "123456") {
            CommonResult.success("admin")
        } else {
            CommonResult.validateFailed()
        }
    }
}

启动两端程序

输入正确的账号密码

输入错误的账号密码

七、GitHub 源码地址

vue 端:https://github.com/xiaojinlai/vue-spring-login-summed

Java 端:https://github.com/xiaojinlai/vue-login-java

Java 端 - Kotlin 版本:https://github.com/xiaojinlai/vue-login-kotlin

注:Kotlin 版本只是我本人用习惯了 Kotlin,就功能而言与 Java 是一样的。大家如果不喜欢可以不用理会,如果有感兴趣的可以看看,Kotlin 是 Google 推出的一种简洁性语言,主推在 Android 上,用习惯后还是蛮喜欢的。学习起来也不难,内容也不多,推荐一个学习 Kotlin 的网址:https://www.kotlincn.net/docs/reference/


(完)

推荐阅读:

B站100K播放量,SpringBoot+Vue前后端分离完整入门教程!

分享一套SpringBoot开发博客系统源码,以及完整开发文档!速度保存!

Github上最值得学习的100个Java开源项目,涵盖各种技术栈!

2020年最新的常问企业面试题大全以及答案

我们今天的关于SpringBoot+Vue实现用户头像修改功能vue头像上传和修改的分享就到这里,谢谢您的阅读,如果想了解更多关于31. springboot+springcloud+vue b2b2c 商城之使用Turbine实现集群监控、Koa & Mongoose & Vue实现前后端分离--11更新用户头像、spring boot+vue实现爬取各大平台每日热榜数据功能、Springboot Vue Login(从零开始实现Springboot+Vue登录)的相关信息,可以在本站进行搜索。

本文标签:

上一篇Parent child mysql

下一篇使用Pytorch和BERT进行多标签文本分类(pytorch 多标签分类)