SpringBoot+Vue实现简单的登录注册功能

文章目录

  • 一、前言
    • 1.开发环境
    • 2.功能
    • 3.项目运行截图
  • 二、撸代码
    • 1.构建前端项目
    • 2.构建后端项目
    • 3.前端页面编写
    • 4.后端代码编写
    • 5.前后端联调
  • 三、小结

一、前言

‍️‍️‍️如果你是一名全干程序员,那前后端跨域问题一定是家常便饭,作者今天带大家写一个最简单的前后端分离登录系统,方便我们理解前后端分离项目的跨域以及数据传输问题。因为要照顾小白,所以我会写的详细一点,不足的地方,大家多多指点,交流啦

项目下载:
前后端登录注册系统源码下载:
gitee:https://gitee.com/wusupweilgy/springboot-vue.git
蓝奏云:https://wwp.lanzoup.com/iYWSU0r6bf7c

代码生成器下载:
gitee:https://gitee.com/wusupweilgy/wusuowei-plus-generator.git
蓝奏云:https://wwp.lanzoup.com/iGIJL0rbvorg

1.开发环境

jdk8+mysql8+vue2+mybatis-plus+springboot

2.功能

1.简单的注册、登录功能。

3.项目运行截图

SpringBoot+Vue实现简单的登录注册功能_第1张图片
SpringBoot+Vue实现简单的登录注册功能_第2张图片
SpringBoot+Vue实现简单的登录注册功能_第3张图片

二、撸代码

1.构建前端项目

这里就默认大家都安装过node.js环境了,我们需要使用npm的vue cli创建vue2的工程,这里我就用原始一点的方法创建前端工程,你也可以使用开发工具创建,都差不多。输入vue create 项目名,这里注意项目名只能是小写

vue create loginandregister-vue

然后选择图中的这个选项,表示自定义创建项目
SpringBoot+Vue实现简单的登录注册功能_第4张图片
再按图中的选择就行,空格是选中,然后回车进入下一步
SpringBoot+Vue实现简单的登录注册功能_第5张图片
选择vue2.x的版本,因为我从vue2开始学的,vue3的一些新特性,配置还不懂(流下无知的泪水)
SpringBoot+Vue实现简单的登录注册功能_第6张图片
这里输入n,使用vue默认的路由,因为vue有两种路由模式,hash和history,这里就不深入了
SpringBoot+Vue实现简单的登录注册功能_第7张图片
然后就一直选第一个吧,毕竟创工程好像没啥好讲的(其实就是懒,不想敲了),最后一个选项输入n,意思是是否将此作为未来项目的预设(是/否) 我这里选择n了
SpringBoot+Vue实现简单的登录注册功能_第8张图片
这杨紫就说明创建成功了,如果创建的很慢,或者出错,可以看看我都这篇文章,我是用这个办法解决的。解决创建vue项目太慢问题
SpringBoot+Vue实现简单的登录注册功能_第9张图片

2.构建后端项目

后端就是使用idea创建springboot工程了,这里展示下项目依赖,你导入就好了

 <dependencies>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <optional>true</optional>
      </dependency>
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.30</version>
      </dependency>
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-boot-starter</artifactId>
          <version>3.5.2</version>
      </dependency>
      <dependency>
          <groupId>org.apache.httpcomponents</groupId>
          <artifactId>httpcore</artifactId>
          <version>4.4.12</version>
      </dependency>
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.83</version>
      </dependency>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
      </dependency>
  </dependencies>

到此后端工程就创建完了,不过我们还可以生成后端的代码,省的我们掉头发了,只要修改指定的配置就会生成controller,service,mapper,model。生成好后,把文件复制到后端工程里。这样我们就写完了一半的代码了。
创建数据库,导入user.sql文件
SpringBoot+Vue实现简单的登录注册功能_第10张图片
然后打开代码生成器工程,里面就一个Generator类,修改我加了TODO注释的地方,运行成功会跳出生成好的文件夹,然后无脑cv到我们刚才创建好的后端工程里,这样我们的后端工程的基本结构就都有了。

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;

/**
 * MyBatis-Plus 代码生成类
 */
public class Generator {

	// TODO 修改服务名   数据表名   包名
	private static final String SERVICE_NAME = "lgy";
	private static final String PACK_NAME = "com.wusuowei";
	//TODO 修改数据库账号
	private static final String DATA_SOURCE_USER_NAME  = "root";
	//TODO 修改数据库密码
	private static final String DATA_SOURCE_PASSWORD  = "mysql";
	//TODO 修改数据库连接
	private static final String DATA_URL = "jdbc:mysql://127.0.0.1:3306/springboot-vue?serverTimezone=UTC&useUnicode=true&useSSL=false&characterEncoding=utf8";

	//TODO 修改要生成的表
	private static final String[] TABLE_NAMES = new String[]{
			"user"
	};


	// TODO 默认生成entity,需要生成DTO修改此变量
	// 一般情况下要先生成 DTO类 然后修改此参数再生成 PO 类。
	private static final Boolean IS_DTO = false;

	public static void main(String[] args) throws IOException {
		// 代码生成器
		AutoGenerator mpg = new AutoGenerator();
		// 选择 freemarker 引擎,默认 Velocity
		mpg.setTemplateEngine(new FreemarkerTemplateEngine());
		// 全局配置
		GlobalConfig gc = new GlobalConfig();
		gc.setFileOverride(true);
		//生成路径
		String path = System.getProperty("user.dir") + "/src/main/java";
		gc.setOutputDir(path);
		//TODO 修改作者名
		gc.setAuthor("lgy");
		gc.setOpen(false);
		gc.setSwagger2(false);
		gc.setServiceName("%sService");
        gc.setBaseResultMap(true);
        gc.setBaseColumnList(true);

		if (IS_DTO) {
			gc.setSwagger2(true);
			gc.setEntityName("%sDTO");
		}
		mpg.setGlobalConfig(gc);

		// 数据库配置
		DataSourceConfig dsc = new DataSourceConfig();
		dsc.setDbType(DbType.MYSQL);
		dsc.setUrl(DATA_URL);
//		dsc.setDriverName("com.mysql.jdbc.Driver");
		dsc.setDriverName("com.mysql.cj.jdbc.Driver");
		dsc.setUsername(DATA_SOURCE_USER_NAME);
		dsc.setPassword(DATA_SOURCE_PASSWORD);
		mpg.setDataSource(dsc);

		// 包配置
		PackageConfig pc = new PackageConfig();
		pc.setModuleName(SERVICE_NAME);
		pc.setParent(PACK_NAME);

		pc.setServiceImpl("service.impl");
		pc.setXml("mapper");
		pc.setEntity("model.po");
		mpg.setPackageInfo(pc);


		// 设置模板
		TemplateConfig tc = new TemplateConfig();
		mpg.setTemplate(tc);

		// 策略配置
		StrategyConfig strategy = new StrategyConfig();
		strategy.setNaming(NamingStrategy.underline_to_camel);
		strategy.setColumnNaming(NamingStrategy.underline_to_camel);
		strategy.setEntityLombokModel(true);
		strategy.setRestControllerStyle(true);
		strategy.setInclude(TABLE_NAMES);
		strategy.setControllerMappingHyphenStyle(true);
		strategy.setTablePrefix(pc.getModuleName() + "_");
		// Boolean类型字段是否移除is前缀处理
		strategy.setEntityBooleanColumnRemoveIsPrefix(true);
		strategy.setRestControllerStyle(true);

		// 自动填充字段配置
		strategy.setTableFillList(Arrays.asList(
				new TableFill("create_date", FieldFill.INSERT),
				new TableFill("change_date", FieldFill.INSERT_UPDATE),
				new TableFill("modify_date", FieldFill.UPDATE)
		));
		mpg.setStrategy(strategy);

		mpg.execute();
		String packname = PACK_NAME;
		packname = packname.replace(".","/");
		path = path+"/"+packname+"/"+SERVICE_NAME;
		System.err.println(path);
		Desktop.getDesktop().open(new File(path));
	}

}

3.前端页面编写

使用webstrom打开前端工程,下载element-ui库,axios库

npm i element-ui -S
npm i axios -S

下载完后在main.js入口文件中导入,注册第三方库

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import axios from 'axios';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false

Vue.prototype.$axios = axios
axios.defaults.baseURL = 'http://localhost:8088'; //后端地址
//注册插件
Vue.use(ElementUI)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

编写登录页Login.vue

<template>
    <div>
        <el-form ref="loginForm" :model="form" :rules="rules" label width=" 80px" class="login-box">
            <h3 class="login-title">欢迎登录</h3>
            <el-form-item label="账号" prop="username">
                <el-input type="text" placeholder=" 请输入账号" v-model="form.username"/>
            </el-form-item>
            <el-form-item label="密码" prop="password">
                <el-input type="password" placeholder=" 请输入密码" v-model=" form.password"/>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" v-on:click="onSubmit('loginForm')">登录</el-button>
                <el-button type="primary" v-on:click="$router.push('/register')">注册</el-button>
            </el-form-item>
        </el-form>
        <el-dialog
                title="温馨提示"
                :visible.sync="dialogVisible"
                width="30%"
                :before-close="handleClose">
            <span>请输入账号和密码</span>
            <span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
      </span>
        </el-dialog>
    </div>
</template>
<script>
    export default {
        name: "Login",
        data() {
            return {

                form: {
                    username: '',
                    password: ''
                },
// 表单验证, 需要在el-form-item- 元素中增加prop属性
                rules: {
                    username: [{required: true, message: '账号不可为空', trigger: 'blur'}],
                    password: [{required: true, message: '密码不可为空', trigger: 'blur'}]
                },
//对话框显示和隐藏
                dialogVisible: false
            }
        },
        methods: {
            handleClose: function () {
                console.log("Handle Close,空函数");
            },
            onSubmit(formName) {
                //为表单绑定验证功能
                this.$refs [formName].validate((valid) => {
                    if (valid) {
                        this.$axios.post('/login', {
                            username: this.form.username,
                            password: this.form.password
                        })
                            .then(response => {
                                if(response.data.code===20000){
                                    //使用vue-router 路由到指定页面,该方式称之为编程式导航
                                    this.$router.push({
                                        path:"/index",
                                        query:{username:response.data.data.username}
                                    });
                                    console.log(response.data);
                                    this.$message.success(response.data.msg)
                                }else{
                                    console.log(response.data);
                                    this.$message.error(this.form.username+response.data.msg)
                                }
                            })
                    } else {
                        this.dialogVisible = true;
                        return false;
                    }
                });
            },

        }
    }
</script>
<style scoped>
  .login-box {
    border: 1px solid #DCDFE6;
    width: 350px;
    margin: 50px auto;
    padding: 35px 35px 15px 35px;
    border-radius: 5px;
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    box-shadow: 0 0 25px #909399;
  }

  .login-title {
    text-align: center;
    margin: 0 auto 40px auto;
    color: #303133;
  }
</style>

注册页面views/Register.vue

<template>
    <div>
        <el-form ref="loginForm" :model="form" :rules="rules" label width=" 80px" class="login-box">
            <h3 class="login-title">欢迎登录</h3>
            <el-form-item label="账号" prop="username">
                <el-input type="text" placeholder=" 请输入账号" v-model="form.username"/>
            </el-form-item>
            <el-form-item label="密码" prop="password">
                <el-input type="password" placeholder=" 请输入密码" v-model=" form.password"/>
            </el-form-item>
            <el-form-item label="确认密码" prop="checkPass">
                <el-input type="password" placeholder=" 请确认密码" v-model="form.checkPass" />
            </el-form-item>
            <el-form-item>
                <el-button type="primary" v-on:click="onSubmit('loginForm')">注册</el-button>
            </el-form-item>
        </el-form>
        <el-dialog
                title="温馨提示"
                :visible.sync="dialogVisible"
                width="30%"
                :before-close="handleClose">
            <span>请输入账号和密码</span>
            <span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
      </span>
        </el-dialog>
    </div>
</template>
<script>
    export default {
        name: "Login",
        data() {
            var validatePass2 = (rule, value, callback) => {
                if (value === '') {
                    callback(new Error('请再次输入密码'));
                } else if (value !== this.form.password) {
                    callback(new Error('两次输入密码不一致!'));
                } else {
                    callback();
                }
            };
            return {
                form: {
                    username: '',
                    password: '',
                    checkPass: '',
                },
// 表单验证, 需要在el-form-item- 元素中增加prop属性
                rules: {
                    checkPass: [{validator: validatePass2, trigger: 'blur'}],
                    username: [{required: true, message: '账号不可为空', trigger: 'blur'}],
                    password: [{required: true, message: '密码不可为空', trigger: 'blur'}]
                },
//对话框显示和隐藏
                dialogVisible: false
            }
        },
        methods: {
            handleClose: function () {
                console.log("Handle Close,空函数");
            },
            onSubmit(formName) {
                //为表单绑定验证功能
                this.$refs [formName].validate((valid) => {
                    if (valid) {
                        this.$axios.post('/register', {
                            username: this.form.username,
                            password: this.form.password
                        })
                            .then(response => {
                                if(response.data.code===20000){
                                    //使用vue-router 路由到指定页面,该方式称之为编程式导航
                                    this.$router.push("/login");
                                    this.$message.success(response.data.msg)
                                }else{
                                    console.log(response.data);
                                    this.$message.error(this.form.username+"已被注册")
                                }
                            })


                    } else {
                        this.dialogVisible = true;
                        return false;
                    }
                });
            },

        },
        created() {
            console.log(this)
        }
    }
</script>
<style scoped>
    .login-box {
        border: 1px solid #DCDFE6;
        width: 350px;
        margin: 50px auto;
        padding: 35px 35px 15px 35px;
        border-radius: 5px;
        -webkit-border-radius: 5px;
        -moz-border-radius: 5px;
        box-shadow: 0 0 25px #909399;
    }

    .login-title {
        text-align: center;
        margin: 0 auto 40px auto;
        color: #303133;
    }
</style>

首页views/Index.vue,就是单纯为了演示功能

<template>
    <div>
        <h1>欢迎{{$route.query.username}}来到首页</h1>
    </div>
</template>

<script>
</script>
<style scoped>
</style>

页面写完了就是配置路由规则了,修改router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import Register from '../views/Register.vue'
import Index from '../views/Index.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path: '/register',
    name: 'Register',
    component: Register
  },
  {
    path: '/index',
    name: 'Index',
    component: Index
  },
]

const router = new VueRouter({
  routes
})
export default router

这时你就可以运行项目了,不过肯定会报错,这是因为eslint校验,需要在vue.config.js中加入lintOnSave:false,到这前端代码就差不多写完了,不过还有坑,不知道你们发现了没,没发现的话,那就等前后端联调的时候再说吧

4.后端代码编写

1)创建application.yml配置文件,配置数据库连接。

server:
  port: 8088
spring:
  application:
    name: content-api
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot-vue?serverTimezone=UTC&userUnicode=true&useSSL=false
    username: root
    password: mysql
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2)定义统一返回结果工具类,这样前端方便取出后端传来的数据

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.http.HttpStatus;

import java.util.HashMap;
import java.util.Map;

/**
 * 返回数据
 *
 * @author Mark [email protected]
 */
public class R extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public R setData(Object data) {
        put("data",data);
        return this;
    }

    //利用fastjson进行反序列化
    public <T> T getData(TypeReference<T> typeReference) {
        Object data = get("data");	//默认是map
        String jsonString = JSON.toJSONString(data);
        T t = JSON.parseObject(jsonString, typeReference);
        return t;
    }

    public R() {
        put("code", 20000);
        put("msg", "success");
    }

    public static R error() {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
    }

    public static R error(String msg) {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }

    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
    public  Integer getCode() {
        return (Integer) this.get("code");
    }
}

3)接口编写(分别是UserService,UserServiceImpl,UserController)

public interface UserService extends IService<User> {

    User getByNameAndPassword(String name,String password);

    User getByName(String name);

    void addUser(User user);
}
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public User getByNameAndPassword(String name,String password) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
        wrapper.eq(User::getPassword,password);
        wrapper.eq(User::getUsername, name);
        return userMapper.selectOne(wrapper);
    }

    @Override
    public User getByName(String name) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>();
        wrapper.eq(User::getUsername, name);
        return userMapper.selectOne(wrapper);
    }

    @Override
    public void addUser(User user) {
        userMapper.insert(user);
    }
}
@Slf4j
@RestController
public class UserController {

    @Autowired
    private UserService  userService;

    @PostMapping("/login")
    public R login(@RequestBody User user){
        String name = user.getUsername();
        String password = user.getPassword();
        User userDB = userService.getByNameAndPassword(name,password);
        if(userDB==null){
            return R.error(20001,"没有该用户");
        }
        return R.ok("登录成功").setData(userDB);

    }

    @PostMapping("/register")
    public R regiseter(@RequestBody User user){
        String name = user.getUsername();
        User checkName = userService.getByName(name);
        if(checkName!=null){
            return R.error(20001,"用户名已被注册");
        }
        userService.addUser(user);
        return R.ok("注册成功").setData(user);

    }
}

4)在启动类上加上@MapperScan("com.wusuowei.lgy.mapper")注解,扫描每个Mapper接口,生成相应的实现类

5.前后端联调

运行前后端项目,进行注册,发现报错,这是因为浏览器的同源策略,前端给后端发请求时,不能进行跨域访问,这里需要我们进行跨域配置
SpringBoot+Vue实现简单的登录注册功能_第11张图片
main.js中修改

axios.defaults.baseURL = '/api'; //后端地址

vue.config.js中添加

  devServer: {
    // 本地配置
    proxy: {
      '/api': {
        target: "http://localhost:8088",//实际访问的ip
        changeOrigin: true,
        pathRewrite: {
          '^/api': "" //实际访问的ip
        }
      },
    }
  }

重启前端项目,这时就可以跨域了,功能都正常了
这里只是实现了基本登录注册功能,其实还有很多逻辑上的问题,比如可以跳过登录,直接访问首页,如果要实现完善的登录注册工程,可以看下一篇进阶文章,我会在本工程的基础上进行改进。
下一篇:SpringBoot+Vue集成JWT实现完善的登录注册功能

三、小结

这个项目的目的时为了帮助大家理解前后端的跨域以及数据如何传输,如果这篇文章有幸帮助到你,希望读者大大们可以给作者点个赞,创作不易,如果有对后端技术、前端领域感兴趣的,可以关注作者,互相交流学习‍️‍️‍️

你可能感兴趣的:(vue.js,spring,boot,java,mysql)