一、前言
😶🌫️😶🌫️😶🌫️如果你是一名全干程序员,那前后端跨域问题一定是家常便饭,作者今天带大家写一个最简单的前后端分离登录系统,方便我们理解前后端分离项目的跨域以及数据传输问题。因为要照顾小白,所以我会写的详细一点,不足的地方,大家多多指点,交流啦😉😉😉
项目下载:
前后端登录注册系统源码下载:
gitee:https://gitee.com/wusupweilgy/springboot-vue.git
代码生成器下载:
gitee:https://gitee.com/wusupweilgy/wusuowei-plus-generator.git
1.开发环境
jdk8+mysql8+vue2+mybatis-plus+springboot
2.功能
1.简单的注册、登录功能。
3.项目运行截图
二、撸代码
1.构建前端项目
这里就默认大家都安装过node.js环境了,我们需要使用npm的vue cli创建vue2的工程,这里我就用原始一点的方法创建前端工程,你也可以使用开发工具创建,都差不多。输入vue create 项目名,这里注意项目名只能是小写
vue create loginandregister-vue
然后选择图中的这个选项,表示自定义创建项目
再按图中的选择就行,空格是选中,然后回车进入下一步
选择vue2.x的版本,因为我从vue2开始学的,vue3的一些新特性,配置还不懂(流下无知的泪水)
这里输入n,使用vue默认的路由,因为vue有两种路由模式,hash和history,这里就不深入了😶
然后就一直选第一个吧,毕竟创工程好像没啥好讲的(其实就是懒,不想敲了😝),最后一个选项输入n,意思是是否将此作为未来项目的预设(是/否) 我这里选择n了
这杨紫就说明创建成功了,如果创建的很慢,或者出错,可以看看我都这篇文章,我是用这个办法解决的。解决创建vue项目太慢问题
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文件
然后打开代码生成器工程,里面就一个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 sunlightcs@gmail.com
*/
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.前后端联调
运行前后端项目,进行注册,发现报错,这是因为浏览器的同源策略,前端给后端发请求时,不能进行跨域访问,这里需要我们进行跨域配置
在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实现完善的登录注册功能
三、小结
😶😶😶这个项目的目的时为了帮助大家理解前后端的跨域以及数据如何传输,如果这篇文章有幸帮助到你,希望读者大大们可以给作者点个赞,创作不易,如果有对后端技术、前端领域感兴趣的,可以关注作者,互相交流学习😶🌫️😶🌫️😶🌫️