1、写统一返回结果包装类
- 在实际开发中,为了降低开发人员之间的沟通成本,一般返回结果会定义成一个统一格式,具体的格式根据实际开发业务不同有所区别,但至少包括三要素:
- code状态码:由后端统一定义各种返回结果的状态码
- message 描述:本次接口调用的结果描述
- data 数据:本次返回的数据。
- 创建common包,创建Result.java
Result.java
package com.xqh.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/*
* 接口统一返回包装类
*
* */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private String code;
private String msg;
private Object data;
//没有数据,成功时
public static Result success(){
return new Result(Constants.CODE_200,"",null);
}
//有数据,成功时
public static Result success(Object data){
return new Result(Constants.CODE_200,"",data);
}
//失败 ,有状态码
public static Result error(String code,String msg){
return new Result(code,msg,null);
}
//失败,默认一个
public static Result error(){
return new Result(Constants.CODE_500,"系统错误",null);
}
}
- 写一个constants,放这些状态码
package com.xqh.common;
public interface Constants {
String CODE_200 ="200"; //成功
String CODE_500 ="500"; //系统错误
String CODE_401="401"; //权限不足
String CODE_400="400"; //参数错误
String CODE_600 ="600"; //其他业务异常
}
- 这些状态码都可以自定义,根据自己的需要,自定义这些状态码
2、改造接口
- 因为写了把结果统一包装了,所以controller里面的接口都要改造成返回Result类
UserController.java
//登录
@PostMapping("/login")
public Result login(@RequestBody UserDTO userDTO){
//先验证都不为空,再去比对数据库
String username = userDTO.getUsername();
String password = userDTO.getPassword();
if (StrUtil.isBlank(username) || StrUtil.isBlank(password)){
return Result.error(Constants.CODE_400,"参数错误");
}
UserDTO dto = userService.login(userDTO);
return Result.success(dto);
}
//插入和修改操作
@PostMapping
public Result save(@RequestBody User user){
return Result.success(userService.saveOrUpdate(user));
}
//查询所有数据
@GetMapping
public Result findAll(){
return Result.success(userService.list());
}
//删除
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id){
return Result.success(userService.removeById(id));
}
//批量删除
@PostMapping ("/del/batch")
public Result deleteBatch(@RequestBody List<Integer>ids){
return Result.success(userService.removeBatchByIds(ids));
}
//根据id查找用户信息
@GetMapping("{id}")
public Result findOneById(@PathVariable Integer id){
return Result.success(userService.getById(id));
}
//分页查询
@GetMapping("/page")
public Result findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize,
@RequestParam(defaultValue = "") String username,
@RequestParam(defaultValue = "") String email,
@RequestParam(defaultValue = "") String address){
return Result.success(userService.findPage(pageNum,pageSize,username,email,address));
}
//导入
@PostMapping("/import")
public Result imp(MultipartFile file) throws Exception{
InputStream inputStream = file.getInputStream();
ExcelReader reader = ExcelUtil.getReader(inputStream);
List<User> list = reader.readAll(User.class); //要对应上自己数据库的字段名
System.out.println(list);
//导入到后端来,进行插入到数据库中
userService.saveBatch(list);
return Result.success(true);
}
- 接口改造后,前端也要该,在响应请求接口的地方,用res.data代替res,后面写注册功能再提供完整代码。
3、全局异常处理
- 自定义全局异常
创建一个新的包exception
创建GlobalExceptionHandle.java
package com.xqh.exception;
import com.xqh.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
@ResponseBody
public Result handle(ServiceException se){
return Result.error(se.getCode(),se.getMessage());
}
}
ServiceException.java
package com.xqh.exception;
import lombok.Getter;
@Getter
public class ServiceException extends RuntimeException {
private String code;
public ServiceException(String code,String msg){
super(msg);
this.code=code;
}
}
- 使用全局异常
之前写的登录的业务代码
UserService.java
//登录
public UserDTO login(UserDTO userDTO) {
User one = getUserInfo(userDTO);
if (one !=null){
BeanUtil.copyProperties(one,userDTO,true);
return userDTO;
}else {
throw new ServiceException(Constants.CODE_600,"用户名或密码错误"); //抛出异常
}
}
4、实现注册
- SpringBoot中写注册接口
- UserController.java
//注册接口
@PostMapping("/register")
public Result register(@RequestBody UserDTO userDTO){
//先验证都不为空,再去添加到数据库
String username = userDTO.getUsername();
String password = userDTO.getPassword();
if (StrUtil.isBlank(username) || StrUtil.isBlank(password)){
return Result.error(Constants.CODE_400,"参数错误");
}
return Result.success(userService.register(userDTO));
}
- UserService.java
public User register(UserDTO userDTO) {
User one = getUserInfo(userDTO);
if (one==null){
one=new User();
BeanUtil.copyProperties(userDTO,one,true);
save(one); //把copy完之后的用户对象存储到数据库
}else {
throw new ServiceException(Constants.CODE_600,"用户已存在");
}
return one;
}
//将相同代码操作封装起来使用更方便,减去重复代码编写
private User getUserInfo(UserDTO userDTO){
QueryWrapper<User>queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",userDTO.getUsername());
queryWrapper.eq("password",userDTO.getPassword());
User one;
try{
one =getOne(queryWrapper);//从数据库中查询用户信息
}catch(Exception e){
LOG.error(e);
throw new ServiceException(Constants.CODE_500,"系统错误");
}
return one;
}
- Vue前端实现注册
- 写注册页面Register.vue
<template>
<div class="wrapper">
<div style="margin: 100px auto;background-color: #fff;width:350px;height:400px;padding:20px;border-radius:10px">
<div style="margin: 20px 0;text-align:center;font-size:24px"><b>注 册</b></div>
<el-form :model="user" :rules="rules" ref="userForm">
<el-form-item prop="username">
<el-input placeholder="请输入用户名" size="medium" style="margin: 10px 0" prefix-icon="el-icon-user" v-model="user.username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input placeholder="请输入密码" size="medium" style="margin: 10px 0" prefix-icon="el-icon-lock" show-password v-model="user.password"></el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input placeholder="请确认密码" size="medium" style="margin: 10px 0" prefix-icon="el-icon-lock" show-password v-model="user.confirmPassword"></el-input>
</el-form-item>
<el-form-item prop="nickname">
<el-input placeholder="请输入昵称" size="medium" style="margin: 10px 0" prefix-icon="el-icon-lock" v-model="user.nickname"></el-input>
</el-form-item>
<el-form-item style="margin: 10px 0;text-align: right">
<el-button type="primary" size="small" autocomplete="off" @click="login">注册</el-button>
<el-button type="warning" size="small" autocomplete="off" @click="$router.push('/login')">返回登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default{
name: "Login",
data(){
return{
user: {},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max:10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur' }
],
nickname: [
{ required: true, message: '请输入昵称', trigger: 'blur' },
{ min: 1, max: 20, message: '长度在 1 到 20 个字符', trigger: 'blur' }
]
}
}
},
methods:{
login(){
//前端不通过的时候登录不去请求后端
this.$refs['userForm'].validate((valid)=>{
if (valid) { //表单检验合法
if(this.user.password!==this.user.confirmPassword){
this.$message.error("两次输入的密码不一致")
return false
}
this.request.post("/user/register",this.user).then(res => {
if(res.code==='200'){
this.$message.success("注册成功!")
this.$router.push("/login")
}else{
this.$message.error(res.msg)
}
})
}
})
}
}
}
</script>
<style>
.wrapper{
height: 100vh;
background-image: linear-gradient(to bottom right,#FC466B, #3F5EFB);
overflow: hidden;
}
</style>
复制登录页面,然后加以修改即可
- 去index.js中注册
{
path: '/register',
name: 'Register',
component:()=>import('../views/Register.vue')
}
- 在登录页面中实现按注册跳转到注册页面
<el-button type="warning" size="small" autocomplete="off" @click="$router.push('/register')">注册</el-button>
- 注册成功后返回到登录页面
5、完善登录
- 登录成功后到主页,显示当前登录用户的昵称
<el-button type="primary" size="small" autocomplete="off" @click="login">登录</el-button>
//写一个登录的方法
<script>
login(){
//前端不通过的时候登录不去请求后端
this.$refs['userForm'].validate((valid)=>{
if (valid) { //表单检验合法
this.request.post("/user/login",this.user).then(res => {
if(res.code==='200'){
localStorage.setItem("user",JSON.stringify(res.data)) //存储用户信息到浏览器里面
this.$router.push("/")
this.$message.success("登录成功!")
}else{
this.$message.error(res.msg)
}
})
}
</script>
- 将登录后,传过来的UserDto储存在浏览器里,后面需要使用直接调用
Header.vue
<span>{{user.nickname}}</span><i class="el-icon-arrow-down" style="margin-left:5px"></i>
data(){
return{
user: localStorage.getItem("user")?JSON.parse(localStorage.getItem("user")):{}
}
logout(){
this.$router.push("/login")
localStorage.removeItem("user")
this.$message.success("退出成功")
}//退出后,删除存储在浏览器的数据
- 效果如图
登录后自动获取当前用户的昵称并显示
6、编写个人信息功能
-
直接注册很多信息不完善,可以在点击个人下面个人信息进行修改
-
编写个人信息页
Person.vue
<template>
<el-card style="width:500px;padding:20px;">
<el-form label-width="80px" size="small">
<el-form-item label="用户名" >
<el-input v-model="form.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="昵称" >
<el-input v-model="form.nickname" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" >
<el-input v-model="form.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="电话" >
<el-input v-model="form.phone" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="地址" >
<el-input v-model="form.address" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">确 定</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
export default{
name:"Person",
data(){
return{
form:{},
user:localStorage.getItem("user")?JSON.parse(localStorage.getItem("user")):{}
}
},
created(){
this.request.get("/user/username/"+this.user.username).then(res=>{
if(res.code==='200'){
this.form=res.data
}
})
},
methods:{
save(){
this.request.post("/user",this.form).then(res=>{
if(res.data){
this.$message.success("保存成功!")
}else{
this.$message.error("保存失败!")
}
})
}
}
}
</script>
<style>
</style>
- 根据登录成功后储存在浏览器中的用户名,来向后端请求当前用户的完整信息
后端通过用户名查询数据的接口:
//根据用户名查找用户信息
@GetMapping("/username/{username}")
public Result findOneByName(@PathVariable String username){
QueryWrapper<User>queryWrapper=new QueryWrapper<>();
queryWrapper.eq("username",username);
return Result.success(userService.getOne(queryWrapper));
}
- 找到后储存在浏览器中,并更新form中的数据,更新为res.data,并显示出来,这样个人信息就拿到了当前用户的信息。然后执行的是更新操作。
- 点击确定即更新成功
7、完整前端代码
- 因为接口改造,导致前端请求接口的代码也要稍作修改。下面是完整代码。
- Header.vue
<template>
<div style="font-size: 12px;line-height:60px;display: flex;">
<div style="flex:1;font-size:18px">
<span :class="collapseBtnClass" style="cursor:pointer" @click="collapse"></span>
<el-breadcrumb separator="/" style="display:inline-block;margin-left: 10px;">
<el-breadcrumb-item :to="'/'">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{currentPathName}}</el-breadcrumb-item>
</el-breadcrumb>
</div>
<el-dropdown style="width:70px;cursor:pointer" >
<span>{{user.nickname}}</span><i class="el-icon-arrow-down" style="margin-left:5px"></i>
<el-dropdown-menu slot="dropdown" style="width:100px; text-align:center">
<el-dropdown-item style="font-size:14px;padding:5px 0">
<router-link to="/person" style="text-decoration:none">个人信息</router-link>
</el-dropdown-item>
<el-dropdown-item style="font-size:14px;padding:5px 0">
<span style="text-decoration: none" @click="logout"> 退出</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default{
name:"Header",
props:{
collapseBtnClass:String,
collapse:Function
},
computed:{
currentPathName(){
return this.$store.state.currentPathName; //需要监听的数据
}
},
data(){
return{
user: localStorage.getItem("user")?JSON.parse(localStorage.getItem("user")):{}
}
},
methods:{
collapse(){
this.$emit("asideCollapse")
},
logout(){
this.$router.push("/login")
localStorage.removeItem("user")
this.$message.success("退出成功")
}
}
}
</script>
<style scoped>
</style>
- Login.vue
<template>
<div class="wrapper">
<div style="margin: 200px auto;background-color: #fff;width:350px;height:300px;padding:20px;border-radius:10px">
<div style="margin: 20px 0;text-align:center;font-size:24px"><b>登 录</b></div>
<el-form :model="user" :rules="rules" ref="userForm">
<el-form-item prop="username">
<el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-user" v-model="user.username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input size="medium" style="margin: 10px 0" prefix-icon="el-icon-lock" show-password v-model="user.password"></el-input>
</el-form-item>
<el-form-item style="margin: 10px 0;text-align: right">
<el-button type="primary" size="small" autocomplete="off" @click="login">登录</el-button>
<el-button type="warning" size="small" autocomplete="off" @click="$router.push('/register')">注册</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
export default{
name: "Login",
data(){
return{
user: {},
rules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max:10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur' }
]
}
}
},
methods:{
login(){
//前端不通过的时候登录不去请求后端
this.$refs['userForm'].validate((valid)=>{
if (valid) { //表单检验合法
this.request.post("/user/login",this.user).then(res => {
if(res.code==='200'){
localStorage.setItem("user",JSON.stringify(res.data)) //存储用户信息到浏览器里面
this.$router.push("/")
this.$message.success("登录成功!")
}else{
this.$message.error(res.msg)
}
})
}
})
}
}
}
</script>
<style>
.wrapper{
height: 100vh;
background-image: linear-gradient(to bottom right,#FC466B, #3F5EFB);
overflow: hidden;
}
</style>
- User.vue
<template>
<div>
<div style="padding:10px 0">
<el-input style="width:200px" placeholder="请输入用户名" suffix-icon="el-icon-search" v-model="username"></el-input>
<el-input style="width:200px" placeholder="请输入邮箱" suffix-icon="el-icon-message" class="ml-5" v-model="email"></el-input>
<el-input style="width:200px" placeholder="请输入地址" suffix-icon="el-icon-position" class="ml-5" v-model="address"></el-input>
<el-button class="ml-5" type="primary" @click="load">搜索</el-button>
<el-button type="warning" @click="reset">重置</el-button>
</div>
<div style="margin:10px 0">
<el-button type="primary" @click="handleAdd">新增<i class="el-icon-circle-plus-outline"></i></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定要删除这些内容吗?"
@confirm="delBatch"
>
<el-button type="danger" slot="reference">批量删除<i class="el-icon-remove-outline"></i></el-button>
</el-popconfirm>
<el-upload action="http://localhost:8081/user/import" :show-file-list="false" accept="'xlsx'" :on-success="handleImportSuccess" style="display:inline-block">
<el-button type="primary" class="ml-5">导入<i class="el-icon-top"></i></el-button>
</el-upload>
<el-button type="primary" @click="exp" class="ml-5">导出<i class="el-icon-bottom"></i></el-button>
</div>
<el-table :data="tableData" border stripe :header-cell-calss-name="'headerBg'" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="username" label="用户名" width="140"></el-table-column>
<el-table-column prop="nickname" label="昵称" width="120"></el-table-column>
<el-table-column prop="email" label="邮箱" ></el-table-column>
<el-table-column prop="phone" label="电话" ></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope" >
<el-button type="success" @click="handleUpdate(scope.row)">编辑 <i class="el-icon-edit"></i></el-button>
<el-popconfirm
class="ml-5"
confirm-button-text='确定'
cancel-button-text='我再想想'
icon="el-icon-info"
icon-color="red"
title="您确定要删除吗?"
@confirm="handleDelete(scope.row.id)"
>
<el-button type="danger" slot="reference">删除<i class="el-icon-remove-outline"></i></el-button>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="padding:10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[2, 4, 6, 10]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<el-dialog title="用户信息" :visible.sync="dialogFormVisible" width="30%">
<el-form label-width="80px" size="small">
<el-form-item label="用户名" >
<el-input v-model="form.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="昵称" >
<el-input v-model="form.nickname" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" >
<el-input v-model="form.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="电话" >
<el-input v-model="form.phone" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="地址" >
<el-input v-model="form.address" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="save">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default{
name:"User",
data() {
return {
tableData:[],
total: 0 ,
pageNum:1,
pageSize:4,
username:"",
email:"",
address:"",
form:{},
dialogFormVisible:false,
multipleSelection:[]
}
},
created(){
this.load()
},
methods:{
load(){
this.request.get("/user/page",{
params:{
pageNum:this.pageNum,
pageSize:this.pageSize,
username:this.username,
email:this.email,
address:this.address
}
}).then(res=>{
console.log(res)
this.tableData=res.data.records
this.total=res.data.total
})
},
save(){
this.request.post("/user",this.form).then(res=>{
if(res.data){
this.$message.success("保存成功!")
this.dialogFormVisible=false
this.load()
}else{
this.$message.error("保存失败!")
}
})
},
handleAdd(){
this.dialogFormVisible=true
this.form={}
},
handleUpdate(row){
this.form={...row}
this.dialogFormVisible=true
},
handleDelete(id){
this.request.delete("/user/" + id).then(res=>{
if(res.data){
this.$message.success("删除成功!")
this.load()
}else{
this.$message.error("删除失败!")
}
})
},
delBatch(){
let ids=this.multipleSelection.map(v=>v.id) //把对象数组转化为id数组【1,2,3】
request.post("/user/del/batch",ids).then(res=>{
if(res.data){
this.$message.success("批量删除成功!")
this.load()
}else{
this.$message.error("批量删除失败!")
}
})
},
handleSelectionChange(val){
this.multipleSelection=val
},
reset(){
this.username=""
this.email=""
this.address=""
this.load()
},
handleSizeChange(pageSize){
this.pageSize=pageSize
this.load()
},
handleCurrentChange(pageNum){
this.pageNum=pageNum
this.load()
},
exp(){
window.open("http://localhost:8081/user/export")
} ,
handleImportSuccess(){
this.$message.success("文件导入成功!")
this.load()
}
}
}
</script>
<style>
.headerBg{
background: #eee!important;
}
</style>
- 改造完代码,每个功能都测试一下。