最近很多小伙伴都在问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头像上传和修改)
- 31. springboot+springcloud+vue b2b2c 商城之使用Turbine实现集群监控
- Koa & Mongoose & Vue实现前后端分离--11更新用户头像
- spring boot+vue实现爬取各大平台每日热榜数据功能
- Springboot Vue Login(从零开始实现Springboot+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
@CrossOrigin
public 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;
@Service
public 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();
}
}
}
}
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 修改原因
*/
@Mapper
public 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);
}
server.port=8005
#server.servlet.context-path=/
spring.datasource.url=jdbc:mysql://localho:3306/tests?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#\u9A7C\u5CF0\u547D\u540D
mybatis.configuration.map-underscore-to-camel-case=true
// 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 = false
Vue.prototype.$axios = axios
/* eslint-disable no-new */
new Vue({
el: ''#app'',
router,
store,
components: { App },
template: ''<App/>''
})
<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>
<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>
<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实现集群监控
前面我们实现了对单个服务实例的监控,当然在实际应用中,单个实例的监控数据没有多大的价值,我们更需要的是一个集群系统的监控信息,这时我们就需要 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更新用户头像
上节回顾
- 更新用户文本数据
工作内容
- 更新用户数据
- 图片上传 & 存储 & 静态化访问
准备工作
-
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
请求结果:没有任何返回。vs code
的调试控制台反馈:
设置存储文件的路径不存在。
保存文件
存储路径不存在,需要检测文件目录是否存在,若不存在,则新建。
// 新建文件: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
,可以在处理文件之前,进行一些操作,如:测试目录是否存在、改名、改存储路径等。
测试结果:
方式二、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
测试结果:
访问文件
这时候访问文件:
所以,需要将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;
}
...
继续访问:
这是因为默认会请求动态资源,而图片数据静态资源
// 更新文件:
...
const koaStatic = require(''koa-static'');
...
// 中间件:指定静态资源路径 vs. 使其与动态资源分离
app.use(koaStatic(path.join(__dirname, ''public/'')))
继续访问,报错如上:
这是因为访问路径错了:访问路径不用带/public
若怀疑,不加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
目录删除,重新测试:
前端页面头像逻辑
//更新文件:
...
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
上传之前,校验文件类型和大小;
效果展示:
参考文档
koa-static
koa-body上传文件相关配置
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登录)
小Hub领读:
一个完整的Spirngboot+vue实现登录的小例子,我之前在vueblog中也搞过,哈哈,再来回顾一下!
作者:Eli Shawhttps://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登录)的相关信息,可以在本站进行搜索。
本文标签: