码云地址
第一章、项目概述
人事管理系统是每个公司必备的管理系统,可以更方便的管理员工。
需求分析
hr实现对员工的增删改查,管理员实现对hr的更改。对员工进行搜索等功能。
总述
本项目是前后端分离项目,在服务器中运行。前端页面友好。
技术栈选择
- 前端:vue3、vite2、axios、router、element UI 、nodejs
- 后端:springboot、springsecurity、springdatajpa、maven、tomcat
环境介绍
- 前端使用webstorm开发
- 后端使用idea开发
- 数据库使用MySQL8.0
- 可视化工具使用Navicat
- JDK版本:15
- maven版本:3.6
- tomcat版本:9.0
- npm版本:7.17
效果图展示
第二章、设计思路
该系统主要是前端设计页面接受后端的JSON数据,前后端所有数据除了登录页面使用key values 形式,其余全部使用JSON格式。后端使用springdatajpa自动生成的restful风格接口。前端通过axios发起请求获取接口数据。但是在开发中,我们需要克服跨域所带来的的问题。部署阶段就不存在跨域,因为我们会把前端打包到后端,一起部署。这样就不涉及到跨域问题了。当然也可以使用nigx进行代理。
数据库设计
数据表结构
员工表:
hr表:
角色表:
第三章、功能设计
- hr登录
- 权限鉴定
- 对hr的增删改查
- 对员工的增删改查
流程图
第四章、功能实现
后端功能实现
环境准备
添加springdata rest
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
目录结构
hr相关功能实现
登录功能
创建vhrdb数据库
配置jpa连接数据库
#配置数据库
spring.datasource.url=jdbc:mysql://localhost:3306/vhrdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
#配置jpa
#1.jpa数据库
spring.jpa.database=mysql
#2.在控制台打印sql
spring.jpa.show-sql=true
#3.jpa数据库平台
spring.jpa.database-platform=mysql
#4.当对象改变更新表
spring.jpa.hibernate.ddl-auto=update
#5.指定方言!!!重要!!!
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
#.....
创建用户相关的表
-
角色表
package com.wz.vhrdb.entity; import javax.persistence.*; /** * @author: 王泽 */ @Entity @Table(name = "t_role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String nameZh; //角色的中文名 public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNameZh() { return nameZh; } public void setNameZh(String nameZh) { this.nameZh = nameZh; } }
-
用户表
package com.wz.vhrdb.entity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import javax.persistence.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * @author: 王泽 */ @Entity(name = "t_user") public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private boolean accountNonExpired; private boolean accountNonLocked; private boolean credentialsNonExpired; private boolean enabled; @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST) private List<Role> roles; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setAccountNonExpired(boolean accountNonExpired) { this.accountNonExpired = accountNonExpired; } public void setAccountNonLocked(boolean accountNonLocked) { this.accountNonLocked = accountNonLocked; } public void setCredentialsNonExpired(boolean credentialsNonExpired) { this.credentialsNonExpired = credentialsNonExpired; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Role role : getRoles()) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return accountNonExpired; } @Override public boolean isAccountNonLocked() { return accountNonLocked; } @Override public boolean isCredentialsNonExpired() { return credentialsNonExpired; } @Override public boolean isEnabled() { return enabled; } }
-
运行程序,创建表。
创建UserDao接口
package com.wz.vhrdb.dao;
import com.wz.vhrdb.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserDao extends JpaRepository<User,Long> {
User findUserByUsername(String username);
}
创建UserService类
package com.wz.vhrdb.service;
import com.wz.vhrdb.dao.UserDao;
import com.wz.vhrdb.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* @author: 王泽
*/
public class UserService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final User user = userDao.findUserByUsername(username);
if (user == null){
throw new UsernameNotFoundException("用户不存在");
}
return user;
}
}
配置springsecurity
package com.wz.vhrdb.config;
import com.wz.vhrdb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author: 王泽
*/
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//密码不加密
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
//校验的数据源
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
//角色关系
@Bean
RoleHierarchy roleHierarchy() {
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_admin > ROLE_user");
return hierarchy;
}
//放行静态资源
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**","/css/**","/image/**");
}
//配置拦截规则和表单配置
//表单配置待完善
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")//具备某个角色
.antMatchers("/user/**").hasAnyRole("admin","user")
.anyRequest().authenticated()//除了上述两个只要登录就能访问
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
}
注册功能
直接在service中添加增添方法,jpa为我们提供了save
//用户注册功能(增加用户)
public User insertUser(User user) {
return userDao.save(user);
}
}
在controller中写接口
/**
* 新增用户 post /users
*/
@PostMapping("")
public User addUser(@RequestBody User user){
return userService.insertUser(user);
}
postman测试
{
"username":"王泽",
"password":"123",
"accountNonExpired":true,
"accountNonLocked":true,
"credentialsNonExpired":true,
"enabled":true,
"roles":[{
"name":"admin",
"nameZh":"管理员"
}]
}
我们可以给实体类设置一些默认值:
private boolean accountNonExpired =true;
private boolean accountNonLocked = true;
private boolean credentialsNonExpired =true;
private boolean enabled =true;
测试:
package com.wz.vhrdb;
import com.wz.vhrdb.dao.UserDao;
import com.wz.vhrdb.entity.Role;
import com.wz.vhrdb.entity.User;
import com.wz.vhrdb.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class VhrdbApplicationTests {
@Autowired
UserServiceImpl userService;
@Test
void contextLoads() {
User u1 = new User();
u1.setUsername("liu");
u1.setPassword("123");
// u1.setAccountNonExpired(true);
// u1.setAccountNonLocked(true);
// u1.setCredentialsNonExpired(true);
// u1.setEnabled(true);
List<Role> rs1 = new ArrayList<>();
Role r1 = new Role();
r1.setName("ROLE_admin");
r1.setNameZh("管理员");
rs1.add(r1);
u1.setRoles(rs1);
userService.insertUser(u1);
User u2 = new User();
u2.setUsername("小刘");
u2.setPassword("123");
// u2.setAccountNonExpired(true);
// u2.setAccountNonLocked(true);
// u2.setCredentialsNonExpired(true);
// u2.setEnabled(true);
List<Role> rs2 = new ArrayList<>();
Role r2 = new Role();
r2.setName("ROLE_user");
r2.setNameZh("普通用户");
rs2.add(r2);
u2.setRoles(rs2);
userService.insertUser(u2);
}
}
优化登录注册
主要问题有两方面:1.不能重复注册 2.密码加密问题
密码加密问题
首先我们注册的时候要添加密码加密
//用户注册功能(增加用户)
public User insertUser(User user) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
user.setPassword(encoder.encode(user.getPassword()));
return userDao.saveAndFlush(user);
}
然后我们需要在springsecurity中配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//密码加密
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
//校验的数据源
@Autowired
UserServiceImpl userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
}
检查重复用户
public User insertUser(User user) {
User username = userDao.findUserByUsername(user.getUsername());
if (username == null) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
user.setPassword(encoder.encode(user.getPassword()));
return userDao.save(user);
}else {
throw new RuntimeException("用户名已经存在");
}
}
}
测试:
我们先用userDao.deleteAll()删除所有记录,然后来测试重复添加
员工相关功能
我们首先需要一个员工的表(实体类),员工有姓名,性别,年龄,电话,部门,入职时间
。
创建实体类Personnel
package com.wz.vhrdb.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;
/**
* @author: 王泽
*/
@Entity(name = "t_personnel")
public class Personnel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String pName;
private String pSex;
private Integer pAge;
private String pClass;
private String pTel;
private Date pJoin;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getpName() {
return pName;
}
public void setpName(String pName) {
this.pName = pName;
}
public String getpSex() {
return pSex;
}
public void setpSex(String pSex) {
this.pSex = pSex;
}
public Integer getpAge() {
return pAge;
}
public void setpAge(Integer pAge) {
this.pAge = pAge;
}
public String getpClass() {
return pClass;
}
public void setpClass(String pClass) {
this.pClass = pClass;
}
public String getpTel() {
return pTel;
}
public void setpTel(String pTel) {
this.pTel = pTel;
}
public Date getpJoin() {
return pJoin;
}
public void setpJoin(Date pJoin) {
this.pJoin = pJoin;
}
}
对于员工表我们的需求有,特定的查询,以及增删改查,分页等。
springdatajpa自带的接口基本足够我们本项目的使用
目前所需要的自动生成的接口有:
1.查询所有用户: http://localhost:8989/users (get)
2.查询所有员工: http://localhost:8989/personnels (get)
3.增加用户hr: http://localhost:8989/user/adduser (post)
4.增加员工: http://localhost:8989/personnels (post)
5.分页查询员工: http://localhost:8989/personnels?page=0&size=5
6.删除hr: http://localhost:8989/users/id (delete)
7.删除员工: http://localhost:8989/personnels/id (delete)
8.修改员工: http://localhost:8989/personnels/id (put)
优化与前端的交互体验
主要思路:成功与失败都有信息来提示前端
编码:
编写vo包(传值的)中的result类
package com.wz.vhrdb.vo;
/**
* @author: 王泽
*/
public class Result {
private Boolean status =true;
private String msg;
public Boolean getStatus() {
return status;
}
public void setStatus(Boolean status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
编写controller
@PostMapping("/adduser")
public Result addUser(@RequestBody User user){
Result result =new Result();
try{
userService.insertUser(user);
result.setMsg("hr增加成功");
}catch (Exception e){
result.setStatus(false);
result.setMsg("新增hr失败"+e.getMessage());
}
return result;
}
其余接口均使用springdatajpa自动生成的接口!
前端功能实现
环境搭建
目录结构
用vite 进行环境搭建
响应尤大的号召,使用vite!
-
npm init @vitejs/app vhrui --template vue
-
cd vhrui
-
npm install
至此项目创建完成!接下来我们需要引入我们所需要的其他组件
引入路由
-
npm install vue-router@next
-
创建router目录来存放router配置
import { createRouter,createWebHistory} from "vue-router"; // 路由信息 const routes = [ { path: "/", name: "Index", component: () => import('../views/idnex.vue'), }, ]; // 导出路由 const router = createRouter({ history: createWebHistory(), routes }); export default router;
-
在main.js中使用路由
import { createApp } from 'vue' import App from './App.vue' import router from "./router/router"; const app = createApp(App) app.mount('#app') app.use(router)
注意:在使用的时候要到inde.vue
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<index></index>
</template>
<script>
import Index from "./views/index.vue";
export default {
components: {Index}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
引入UI组件库
npm install element-plus --save
在main.js中使用
import { createApp } from 'vue'
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
import App from './App.vue';
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
引入axios
npm install --save axios vue-axios
在main.js 中使用
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
使用方法:
this.$http.get(api).then((response) => {
console.log(response.data)
})
开发界面
项目路由:
import { createRouter,createWebHistory} from "vue-router";
// 路由信息
const routes = [
{
path: "/",
name: "index",
component: () => import('../components/index.vue'),
},
{
path: "/login",
name: "login",
component: () => import('../components/login.vue'),
},
{
path: "/register",
name: "register",
component: () => import('../views/user/add.vue'),
},
{
path: "/personnel",
name: "personnel",
component: () => import('../components/personnel.vue'),
children:[
{
path: "/users",
name: "users",
component: () => import('../views/user/users.vue'),
},
{
path: "/pindex",
name: "pindex",
component: () => import('../views/personnel/pindex.vue'),
},
{
path: "/class",
name: "class",
component: () => import('../views/personnel/class.vue'),
},
{
path: "/personnels",
name: "personnels",
component: () => import('../views/personnel/personnels.vue'),
},
]
},
];
// 导出路由
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
组件的开发
这里只写出首页的主要代码
<template>
<div class="bg">
<div id="b">
<el-container>
<el-aside width="50px">
<div style="height: 800px;">
<el-steps direction="vertical" >
<el-step title="后端攻城狮"></el-step>
<el-step title="前端攻城狮"></el-step>
<el-step title="全栈工程师" ></el-step>
</el-steps>
</div>
</el-aside>
<el-container>
<el-header>
<el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
<el-menu-item index="pindex" style="color:darkorange"><strong>公司主页</strong></el-menu-item>
<el-menu-item index="personnels" style="color: darkmagenta"><strong>员工管理</strong></el-menu-item>
<el-menu-item index="class" style="color: darkgreen" disabled><strong>部门管理</strong></el-menu-item>
<el-menu-item index="users" style="color: darkcyan"><strong>hr管理</strong></el-menu-item>
<el-menu-item index="/" style="color: red" @click="logout">退出</el-menu-item>
</el-menu>
</el-header>
<el-main>
<router-view/>
</el-main>
</el-container>
</el-container>
</div>
</div>
</template>
<script>
export default {
name: "personnel",
data() {
return {
activeIndex: 'pindex'
};
},
methods: {
handleSelect(key, keyPath) {
console.log(key, keyPath);
this.$router.push(key);
},
logout(){
this.$http.get('http://localhost:3000/logout')
}
},
created() {
this.$router.push("pindex");
}
}
</script>
<style scoped>
.bg{
background-image:url(../assets/image/1.jpg);
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
background-attachment: local;
background-repeat: no-repeat;
background-size: cover;
float: left;
-o-background-size: cover;
background-position: center;
}
#b{ background:#000;
color: #00ff22;
filter:alpha(Opacity=60);
-moz-opacity:0.8;opacity: 0.8
}
</style>
第五章、前后端整合所遇到的问题
1.springsecurity+vue如何实现登录验证??
在前后端分离这样的开发架构下,前后端的交互都是通过 JSON 来进行,无论登录成功还是失败,都不会有什么服务端跳转或者客户端跳转之类。
登录成功了,服务端就返回一段登录成功的提示 JSON 给前端,前端收到之后,该跳转该展示,由前端自己决定,就和后端没有关系了。
登录失败了,服务端就返回一段登录失败的提示 JSON 给前端,前端收到之后,该跳转该展示,由前端自己决定,也和后端没有关系了。
successHandler 的功能十分强大,甚至已经囊括了 defaultSuccessUrl 和 successForwardUrl 的功能。
.successHandler((req, resp, authentication) -> {
Object principal = authentication.getPrincipal();
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(principal));
out.flush();
out.close();
})
2.springdatajpa+springsecurity+vue如何实现跨域?
只有开发时存在这种跨域的情况。
解决思路:
- 部署:前端部署到nigx上,后端通过nigx做请求转发。或者部署到一起
- 开发环境下:用node.js 做请求转发。
正解:
https://my.oschina.net/u/1020373/blog/4899705
3.后端传来的json数据如何与前端交互?
前端接收:发送axios异步请求,用tableData接收
//tableData[]
findAll(){
this.axios.get('http://localhost:8989/users').then(res=>{
this.tableData=res.data;
})
}
// 初始化的时候调用方法
created() {
this.findAll();
}
前端发来的post,后端接收
这时候,我们就需要再复习一下后端所用到的一些注解:
@RequestBody :可以将body里面所有的json数据传到后端,后端再进行解析。
@RequestParam :接收的参数是来自requestHeader中,即请求头。通常用于GET请求
-
@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。在后端的同一个接收方法里,@RequestBody与@RequestBody可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
-
注解@RequestParam接收的参数是来自requestHeader中,即请求头。通常用于GET请求,比如常见的url:http://localhost:8081/spring-boot-study/novel/findByAuthorAndType?author=唐家三少&type=已完结
-
@RequestParam有三个配置参数:
required
表示是否必须,默认为true
,必须。defaultValue
可设置请求参数的默认值。value
为接收url的参数名(相当于key值)。
-
如果参数前写了@RequestParam(xxx),那么前端必须有对应的xxx名字才行(不管其是否有值,当然可以通
过设置该注解的required属性来调节是否必须传),如果没有xxx名的话,那么请求会出错,报400。 -
如果参数前不写@RequestParam(xxx)的话,那么就前端可以有可以没有对应的xxx名字才行,如果有xxx名
的话,那么就会自动匹配;没有的话,请求也能正确发送。 -
@JsonAlias注解,实现:json转模型时,使json中的特定key能转化为特定的模型属性;但是模型转json时,
对应的转换后的key仍然与属性名一致.@JsonAlias("Name","name123") private String name;
此时,json字符串转换为模型时,json中key为Name或为name123或为name的都能识别。
结论②:@JsonProperty注解,实现:json转模型时,使json中的特定key能转化为指定的模型属性;同样的,模
型转json时,对应的转换后的key为指定的key,见:示例中的motto字段的请求与响应。
以下图进一步说明:@JsonProperty("name123") private String name;
此时,json字符串转换为模型时,key为name123的能识别,但key为name的不能识别。
结论③:@JsonAlias注解需要依赖于setter、getter,而@JsonProperty注解不需要。
结论④:在不考虑上述两个注解的一般情况下,key与属性匹配时,默认大小写敏感。
结论⑤:有多个相同的key的json字符串中,转换为模型时,会以相同的几个key中,排在最后的那个key的值给模
型属性复制,因为setter会覆盖原来的值。见示例中的gender属性。结论⑥:后端@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面
的类)时,会根据json字符串中的key来匹配对应实体类的属性,如果匹配一致且json中的该key对应的值
符合(或可转换为)实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性。
4.后端返回的msg如何显示到前端?
后端:
package com.wz.vhrdb.vo;
/**
* @author: 王泽
*/
public class Result {
private Boolean status =true;
private String msg;
public Boolean getStatus() {
return status;
}
public void setStatus(Boolean status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
@PostMapping("/adduser")
public Result addUser(@RequestBody User user){
Result result =new Result();
try{
userService.insertUser(user);
result.setMsg("hr增加成功");
}catch (Exception e){
result.setStatus(false);
result.setMsg("新增hr失败"+e.getMessage());
}
return result;
}
前端:
submitForm(){
//提交表单到后端
this.$http.post("http://localhost:8989/user/adduser",this.pForm).then(res=>{
console.log(res.data);
if(res.data.status){
this.$message({
message:'恭喜你'+res.data.msg,
type:'success'
})
// 成功后的处理:清空表单信息,刷新所有
this.pForm={};
this.$emit('findAll');
}else {
this.$message.error(this.date.msg);
}
})
},
5.后端传来的数据中嵌套“…”:[{“…”:“…”}]该怎么交互??
6.url中字组件使用父组件的表格行id?
问题等价于父组件向子组件传值!
-
在父组件的
子组件标签
写要传的值 -
<span><put-user :id="uid"></put-user></span>
-
在子组件中使用
-
props:['id'], methods: { submitForm() { console.log(this.id); this.$http.put("http://localhost:8989/users/"+this.id).then(res=>{ console.log(res.data); this.$emit('findAll'); }) }, }
7.登录的值是json,但是后台用的key values??怎么转换
导入 qs;
import qs from 'qs';
然后data 使用data:qs.stringify(param)
let params = {
username: this.loginForm.username,
password: this.loginForm.password
};
console.log(params);
let kv =qs.stringify(params);
console.log(kv);
this.$http.post('http://localhost:8989/login',kv)
8.关于项目的是否有状态登录
这前后端分离开发后,认证这一块到底是使用传统的 session 还是使用像 JWT 这样的 token 来解决呢?
这确实代表了两种不同的方向。
传统的通过 session 来记录用户认证信息的方式我们可以理解为这是一种有状态登录,而 JWT 则代表了一种无状态登录。可能有小伙伴对这个概念还不太熟悉,我这里就先来科普一下有状态登录和无状态登录。
8.1 什么是有状态
有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如 Tomcat 中的 Session。例如登录:用户登录后,我们把用户的信息保存在服务端 session 中,并且给用户一个 cookie 值,记录对应的 session,然后下次请求,用户携带 cookie 值来(这一步有浏览器自动完成),我们就能识别到对应 session,从而找到用户的信息。这种方式目前来看最方便,但是也有一些缺陷,如下:
- 服务端保存大量数据,增加服务端压力
- 服务端保存用户状态,不支持集群化部署
8.2 什么是无状态
微服务集群中的每个服务,对外提供的都使用 RESTful 风格的接口。而 RESTful 风格的一个最重要的规范就是:服务的无状态性,即:
- 服务端不保存任何客户端请求者信息
- 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份
那么这种无状态性有哪些好处呢?
- 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器
- 服务端的集群和状态对客户端透明
- 服务端可以任意的迁移和伸缩(可以方便的进行集群化部署)
- 减小服务端存储压力
8.3 如何实现无状态
无状态登录的流程:
- 首先客户端发送账户名/密码到服务端进行认证
- 认证通过后,服务端将用户信息加密并且编码成一个 token,返回给客户端
- 以后客户端每次发送请求,都需要携带认证的 token
- 服务端对客户端发送来的 token 进行解密,判断是否有效,并且获取用户登录信息
8.4 各自优缺点
使用 session 最大的优点在于方便。你不用做过多的处理,一切都是默认的即可。松哥本系列前面几篇文章我们也都是基于 session 来讲的。
但是使用 session 有另外一个致命的问题就是如果你的前端是 Android、iOS、小程序等,这些 App 天然的就没有 cookie,如果非要用 session,就需要这些工程师在各自的设备上做适配,一般是模拟 cookie,从这个角度来说,在移动 App 遍地开花的今天,我们单纯的依赖 session 来做安全管理,似乎也不是特别理想。
这个时候 JWT 这样的无状态登录就展示出自己的优势了,这些登录方式所依赖的 token 你可以通过普通参数传递,也可以通过请求头传递,怎么样都行,具有很强的灵活性。
第六章、总结
此项目是我写第一个前后端分离项目,项目功能很简单,但是遇到的问题很多,解决问题的过程让我对前后端分离的开发有了更深刻的理解,也让我对springsecurity的安全更加敬佩。对于前端vue中router以及axios的使用更加熟练,学会了ui组件库的使用方法。对于后端,对springboot以及springdatajpa和springsecurity都有新的收获!springboot+vue的开发方式更明确的定位了程序员的分工。但是一个出色的程序员我认为应该是对技术栈都有所掌握的。当然一种技术有更深的造诣是我们所追求的,但是知识面千万不能窄。这就是我本次项目开发的总结。
第七章、致谢
本次项目开发,感谢孙老师的课程,他的课程垫定了我的spring基础。还有百知教育的陈老师,讲的vue与elementui课程很好。感谢 王松(江南一点雨)的springboot与springsecurity以及springdatajpa,mybatis…的教学。让我真正的学会了开发知识!
第八章、参考文献
springboot官方文档
vue官方文档
axios官方文档
element ui 官方文档
vite官方文档
深入浅出springsecurity ——王松(江南一点雨)
MySQL官方文档