vhr
vue-cli3构建vue项目
vue.js官网,安装,命令行工具,vue-cli文档,npm安装
npm install -g @vue/cli
vue --version
vue create vuehr
default
cd vuehr
npm run serve
项目结构
main.js->App.vue->router.js->login.vue
package.json工具包
安装element插件
npm i element-ui -S
引入
import ElementUI from 'element-ui';
Vue.use(ElementUI);
微人事登录页面制作 处理前端登录事件
login.vue
<template>
<div>
<el-form :rules="rules" ref="loginForm" v-loading="loading"
element-loading-text="正在登录..."
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)" :model="loginForm" class="loginContainer">
<h3 class="LoginTitle">系统登录</h3>
<el-form-item prop="username">
<el-input type="text" v-model="loginForm.username" auto-complete="off" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input size="normal" type="text" v-model="loginForm.password" auto-complete="off" placeholder="请输入密码" @keydown.enter.native="submitLogin"></el-input>
</el-form-item>
<el-checkbox size="normal" class="LoginRemember" v-model="checked"></el-checkbox>
<el-button size="normal" type="primary" style="width:100%;" @click="submitLogin">登录</el-button>
</el-form>
</div>
</template>
<script>
export default {
name: "Login",
data(){
return{
loading:false,
checked:true,
loginForm:{
username:'admin',
password: '123'
},
rules:{
username:[{required:true,message:'请输入用户名',trigger:'blur'}],
password:[{required:true,message:'请输入密码',trigger:'blur'}]
}
}
},
methods:{
submitLogin() {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.loading = true;
// resp服务端返回的json
this.postKeyValueRequest('/doLogin',this.loginForm).then(resp=>
{
this.loading=false;
if(resp){
//用户登陆成功后的数据将被保存再sessionStorage中,防止用户刷新后数据丢失,
// 以字符串形式存入,取的时候转为json
window.sessionStorage.setItem("user",JSON.stringify(resp));
//跳转后能否到重定向页面中
let path=this.$route.query.redirect;
// 登录成功后进行页面跳转
this.$router.replace((path==='/'||path===undefined)?'/home':path)
}
})
} else {
this.$message.error("请输入所有字段");
return false;
}
});
}
}
}
</script>
<style>
.loginContainer{
border-radius:15px;
background-clip: padding-box;
margin: 180px auto;
width:350px;
padding:15px 35px 15px 35px;
background: #f6f6f6;
border:1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
}
.LoginTitle{
margin: 15px auto 20px auto;
text-align: center;
}
.LoginRemember{
text-align: left;
margin:0px 0px 15px 0;
}
</style>
home页页面跳转
<div>
home页
</div>
服务端环境搭建
创建vhr数据库,导入表
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.javagirl</groupId>
<artifactId>vhr_project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>vhr_project</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
<scope>runtime</scope>
</dependency>
<!-- 数据库驱动-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-devtools</artifactId>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
</dependencies>
<build>
<!-- maven文件配置识别mapper的xml文件-->
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<plugins>
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- </plugin>-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- <plugin>-->
<!-- <groupId>org.apache.maven.plugins</groupId>-->
<!-- <artifactId>maven-surefire-plugin</artifactId>-->
<!-- <configuration>-->
<!-- <skipTests>true</skipTests>-->
<!-- </configuration>-->
<!-- </plugin>-->
</plugins>
</build>
</project>
使用逆向工程生成mapper和model文件
主运行文件配置mapperScan
@SpringBootApplication
@MapperScan(basePackages = "org.javagirl.vhr_project.mapper")
public class VhrProjectApplication {
public static void main(String[] args) {
SpringApplication.run(VhrProjectApplication.class, args);
}
}
配置application文件
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3307/vhr?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
server.port=8085
服务端登录接口制作
model中的hr
package org.javagirl.vhr_project.model;
/**
* Hr类,实现UserDetails接口的方法
* 不涉及到账户的锁定、密码的过期等等,只有账户是否被禁用,因此只处理了isEnabled方法
*
* */
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class Hr implements UserDetails {
private Integer id;
private String name;
private String phone;
private String telephone;
private String address;
private Boolean enabled;
private String username;
private String password;
private String userface;
public Boolean getEnabled() {
return enabled;
}
private String remark;
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
private List<Role> roles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone == null ? null : phone.trim();
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone == null ? null : telephone.trim();
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address == null ? null : address.trim();
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getUsername() {
return username;
}
// 账户是否过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 账户是否被锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 密码是否过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 账户是否允许被使用
@Override
public boolean isEnabled() {
return enabled;
}
public void setUsername(String username) {
this.username = username == null ? null : username.trim();
}
//获取当前用户所具有的角色
@Override
//生成json时忽略该属性
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>(roles.size());
//roles属性描述当前用户的角色
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
}
public String getUserface() {
return userface;
}
public void setUserface(String userface) {
this.userface = userface == null ? null : userface.trim();
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark == null ? null : remark.trim();
}
}
HrService
package org.javagirl.vhr_project.service;
import org.javagirl.vhr_project.mapper.HrMapper;
import org.javagirl.vhr_project.mapper.HrRoleMapper;
import org.javagirl.vhr_project.model.Hr;
import org.javagirl.vhr_project.utils.HrUtils;
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;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* HrService实现UserDetailsService接口
* 1.根据用户名查找用户
* 2.查找、更新、删除hr
* 3.更新hr角色
*/
@Service
public class HrService implements UserDetailsService {
@Autowired
HrMapper hrMapper;
@Autowired
HrRoleMapper hrRoleMapper;
//在执行登陆的过程中,这个方法根据用户名取查找用户
//如果用户不存在,抛出异常,否则返回Hr
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Hr hr = hrMapper.loadUserByUsername(username);
if(hr == null){
throw new UsernameNotFoundException("用户名不存在");
}
hr.setRoles(hrMapper.getHrRolesById(hr.getId()));
return hr;
}
/**
* 查找所有Hr
* @param keywords
* @return List
*/
public List<Hr> getAllHrs(String keywords) {
return hrMapper.getAllHrs(HrUtils.getCurrentHr().getId(),keywords);
}
/**
* 更新hr
* @param hr
* @return hr.id
*/
public Integer updateHr(Hr hr) {
return hrMapper.updateByPrimaryKeySelective(hr);
}
/**
* 更新hr的角色
* @param hrid
* @param rids
* @return boolean
*/
@Transactional
public boolean updateHrRole(Integer hrid, Integer[] rids) {
//先删除hr的角色
hrRoleMapper.deleteByHrid(hrid);
//再给hr添加角色
return hrRoleMapper.addRole(hrid,rids)==rids.length;
}
/**
* 删除hr
* @param id
* @return int
*/
public Integer deleteHrById(Integer id) {
return hrMapper.deleteByPrimaryKey(id);
}
}
HrMapper接口
package org.javagirl.vhr_project.mapper;
import org.apache.ibatis.annotations.Param;
import org.javagirl.vhr_project.model.Hr;
import org.javagirl.vhr_project.model.Role;
import java.util.List;
public interface HrMapper {
int deleteByPrimaryKey(Integer id);
int insert(Hr record);
int insertSelective(Hr record);
Hr selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(Hr record);
int updateByPrimaryKey(Hr record);
Hr loadUserByUsername(String username);
List<Role> getHrRolesById(Integer id);
List<Hr> getAllHrs(@Param("hrid") Integer hrid,String keywords);
}
HrMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="org.javagirl.vhr_project.mapper.HrMapper" >
<resultMap id="BaseResultMap" type="org.javagirl.vhr_project.model.Hr" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="phone" property="phone" jdbcType="CHAR" />
<result column="telephone" property="telephone" jdbcType="VARCHAR" />
<result column="address" property="address" jdbcType="VARCHAR" />
<result column="enabled" property="enabled" jdbcType="BIT" />
<result column="username" property="username" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="userface" property="userface" jdbcType="VARCHAR" />
<result column="remark" property="remark" jdbcType="VARCHAR" />
</resultMap>
<resultMap id="HrWithRoles" type="org.javagirl.vhr_project.model.Hr" extends="BaseResultMap">
<collection property="roles" ofType="org.javagirl.vhr_project.model.Role">
<id column="rid" property="id"/>
<result column="rname" property="name"/>
<result column="rnameZh" property="nameZh"></result>
</collection>
</resultMap>
<sql id="Base_Column_List" >
id, name, phone, telephone, address, enabled, username, password, userface, remark
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from hr
where id = #{id,jdbcType=INTEGER}
</select>
<select id="loadUserByUsername" resultMap="BaseResultMap"
>
select * from hr where username=#{username}
</select>
<select id="getHrRolesById" resultType="org.javagirl.vhr_project.model.Role"
parameterType="java.lang.Integer">
SELECT r.* FROM role r,hr_role hrr WHERE hrr.`rid`=r.`id` AND hrr.`hrid`=#{id}
</select>
<select id="getAllHrs" resultMap="HrWithRoles">
SELECT hr.id, hr.name, hr.phone, hr.telephone, hr.address, hr.enabled, hr.username, hr.userface, hr.remark,r.`id` AS rid,r.name AS rname,r.`nameZh` AS rnameZh FROM hr LEFT JOIN hr_role hrr ON hr.`id`=hrr.hrid LEFT JOIN role r ON hrr.`rid`=r.`id` WHERE hr.id!=#{hrid} <if test="keywords!=null">and hr.name like concat('%',#{keywords},'%')</if> order by hr.id
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from hr
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="org.javagirl.vhr_project.model.Hr" >
insert into hr (id, name, phone,
telephone, address, enabled,
username, password, userface,
remark)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{phone,jdbcType=CHAR},
#{telephone,jdbcType=VARCHAR}, #{address,jdbcType=VARCHAR}, #{enabled,jdbcType=BIT},
#{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{userface,jdbcType=VARCHAR},
#{remark,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="org.javagirl.vhr_project.model.Hr" >
insert into hr
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
id,
</if>
<if test="name != null" >
name,
</if>
<if test="phone != null" >
phone,
</if>
<if test="telephone != null" >
telephone,
</if>
<if test="address != null" >
address,
</if>
<if test="enabled != null" >
enabled,
</if>
<if test="username != null" >
username,
</if>
<if test="password != null" >
password,
</if>
<if test="userface != null" >
userface,
</if>
<if test="remark != null" >
remark,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
#{id,jdbcType=INTEGER},
</if>
<if test="name != null" >
#{name,jdbcType=VARCHAR},
</if>
<if test="phone != null" >
#{phone,jdbcType=CHAR},
</if>
<if test="telephone != null" >
#{telephone,jdbcType=VARCHAR},
</if>
<if test="address != null" >
#{address,jdbcType=VARCHAR},
</if>
<if test="enabled != null" >
#{enabled,jdbcType=BIT},
</if>
<if test="username != null" >
#{username,jdbcType=VARCHAR},
</if>
<if test="password != null" >
#{password,jdbcType=VARCHAR},
</if>
<if test="userface != null" >
#{userface,jdbcType=VARCHAR},
</if>
<if test="remark != null" >
#{remark,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="org.javagirl.vhr_project.model.Hr" >
update hr
<set >
<if test="name != null" >
name = #{name,jdbcType=VARCHAR},
</if>
<if test="phone != null" >
phone = #{phone,jdbcType=CHAR},
</if>
<if test="telephone != null" >
telephone = #{telephone,jdbcType=VARCHAR},
</if>
<if test="address != null" >
address = #{address,jdbcType=VARCHAR},
</if>
<if test="enabled != null" >
enabled = #{enabled,jdbcType=BIT},
</if>
<if test="username != null" >
username = #{username,jdbcType=VARCHAR},
</if>
<if test="password != null" >
password = #{password,jdbcType=VARCHAR},
</if>
<if test="userface != null" >
userface = #{userface,jdbcType=VARCHAR},
</if>
<if test="remark != null" >
remark = #{remark,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="org.javagirl.vhr_project.model.Hr" >
update hr
set name = #{name,jdbcType=VARCHAR},
phone = #{phone,jdbcType=CHAR},
telephone = #{telephone,jdbcType=VARCHAR},
address = #{address,jdbcType=VARCHAR},
enabled = #{enabled,jdbcType=BIT},
username = #{username,jdbcType=VARCHAR},
password = #{password,jdbcType=VARCHAR},
userface = #{userface,jdbcType=VARCHAR},
remark = #{remark,jdbcType=VARCHAR}
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>
securityConfig
respBean
package org.javagirl.vhr_project.model;
/**
* 返回数据封装
*/
public class RespBean {
private RespBean(Integer status, String msg, Object obj) {
this.status = status;
this.msg = msg;
this.obj = obj;
}
private RespBean() {
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
private Integer status;
private String msg;
private Object obj;
// 返回json格式文件
public static RespBean ok(String msg){
return new RespBean(200,msg,null);
}
public static RespBean ok(String msg,Object obj){
return new RespBean(200,msg,obj);
}
public static RespBean error(String msg){
return new RespBean(500,msg,null);
}
public static RespBean error(String msg,Object obj){
return new RespBean(500,msg,obj);
}
}
ctrl+H查看继承类
package org.javagirl.vhr_project.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.javagirl.vhr_project.model.Hr;
import org.javagirl.vhr_project.model.RespBean;
import org.javagirl.vhr_project.service.HrService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 对登录页面进行处理
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
HrService hrService;
@Autowired
CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
@Autowired
CustomUrlDecisionManager customUrlDecisionManager;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//加载hrservice安全管理
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(hrService);
}
// 对登录页面进行放权
@Override
public void configure(WebSecurity web) throws Exception {
// web.ignoring().antMatchers("/login","/css/**","/js/**","/index.html","/img/**","/fonts/**","favicon.ico");
web.ignoring().antMatchers("/login");
}
//通过withObjectPostProcessor将刚刚创建的CustomFilterInvocationSecurityMetadataSource和CustomUrlDecisionManager注入。请求都会经过刚才的过滤器(除了configure(WebSecurity web)方法忽略的请求)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// .anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(customUrlDecisionManager);
object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
return object;
}
})
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/doLogin")
//重新配置登录页
.loginPage("/login")
// 登录成功,配置登录成功时返回的JSON,登录成功时返回当前用户的信息
.successHandler(new AuthenticationSuccessHandler() {
//登录成功的回调
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out=resp.getWriter();
Hr hr=(Hr)authentication.getPrincipal();
hr.setPassword(null);
RespBean ok = RespBean.ok("登陆成功", hr);
String s = new ObjectMapper().writeValueAsString(hr);
out.write(s);
out.flush();
out.close();
}
})
// 登录失败,表示登录失败,根据不同的异常输出不同的错误提示
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException exception) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out=resp.getWriter();
RespBean respBean = RespBean.error("登陆失败");
// 查看异常的父类 ctrl H
if(exception instanceof LockedException){
respBean.setMsg("账户被锁定");
}else if(exception instanceof CredentialsExpiredException){
respBean.setMsg("密码过期");
}else if(exception instanceof AccountExpiredException){
respBean.setMsg("账户过期");
}else if(exception instanceof DisabledException){
respBean.setMsg("账户被禁用");
}else if(exception instanceof BadCredentialsException){
respBean.setMsg("用户名或者密码输入错误,请重新输入");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
})
.permitAll()
.and()
.logout()
// 注销登录
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功")));
out.flush();
out.close();
}
})
.permitAll()
.and()
//开启测试
.csrf().disable().exceptionHandling()
// 没有认证时,在这里处理结果,不要重定向
.authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
resp.setStatus(401);
PrintWriter out=resp.getWriter();
RespBean respBean = RespBean.error("访问失败");
// 查看异常的父类 ctrl H
if(authException instanceof InsufficientAuthenticationException){
respBean.setMsg("请求失败,请联系管理员");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
});
}
}
package org.javagirl.vhr_project.controller;
import org.javagirl.vhr_project.model.RespBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 配置登录页面
*/
@RestController
public class LoginController {
@GetMapping("/login")
public RespBean login(){
return RespBean.error("尚未登陆,请登陆");
}
}
/doLogin为登录接口,自己定义一个helloController接口进行测试
前端请求方法封装
npm install axios
api.js
import axios from 'axios'
import {Message} from "element-ui";
import router from '../router'
/**
* 响应拦截器
* 采用axios处理网络请求,避免在每次请求时判断各种网络情况(连接超时,服务器内部错误,权限不足等),对axios进行简单的封装,使用axios的拦截器功能
*/
axios.interceptors.response.use(success=>{
// 展示错误信息
if(success.status && success.status===200 &&success.data.status===500){
//业务错误
Message.error({message:success.data.msg})
//返回空
return;
}
//成功,自动提示服务端message
if(success.data.msg){
Message.success({message:success.data.msg})
}
//返回到请求调用的地方
return success.data;
},error=>{
if(error.response.status===504||error.response.status===404){
Message.error({message:'服务器被吃了------'})
}else if(error.response.status===403){
Message.error({message:'权限不足,请联系管理员'})
}else if(error.response.status===401){
Message.error({message:'尚未登陆,请登陆'})
router.replace('/')
}else{
if(error.response.data.msg){
// 服务端有错误消息
Message.error({message:error.response.data.msg})
}else{
//服务端没有返回错误信息
Message.error({message:'未知错误'})
}
}
return;
})
let base='';
//登录请求默认key value形式传递参数,不支持json数据格式传参
export const postKeyValueRequest=(url,params)=>{
return axios({
method:'post',
// 不是单引号,表示变量
url:`${base}${url}`,
data:params,
transformRequest:[function (data){
let ret='';
for(let i in data){
ret+=encodeURIComponent(i)+'='+encodeURIComponent(data[i])+'&'
}
return ret;
}],
headers:{
'Content-Type':'application/x-www-form-urlencoded'
}
});
}
//封装请求方法,post put get delete,json形式传递数据
// 将其做成插件,不然每次使用都要加载 main.js
export const postRequest=(url,params)=>{
return axios({
method:'post',
url:`${base}${url}`,
data:params
})
}
export const putRequest=(url,params)=>{
return axios({
method:'put',
url:`${base}${url}`,
data:params
})
}
export const getRequest=(url,params)=>{
return axios({
method:'get',
url:`${base}${url}`,
data:params
})
}
export const deleteRequest=(url,params)=>{
return axios({
method:'delete',
url:`${base}${url}`,
data:params
})
}
main.js中导入方法
/**
* 将请求方法挂到vue上,后面需要发送网络请求时,不需要导入api
*/
//1.导入所有的请求方法
import {postRequest} from "@/utils/api";
import {postKeyValueRequest} from "@/utils/api";
import {putRequest} from "@/utils/api";
import {deleteRequest} from "@/utils/api";
import {getRequest} from "@/utils/api";
import {initMenu} from "@/utils/menu";
import 'font-awesome/css/font-awesome.min.css'
//2.将请求方法添加到vue.prototype上
Vue.prototype.postRequest = postRequest;
Vue.prototype.postKeyValueRequest = postKeyValueRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.deleteRequest = deleteRequest;
Vue.prototype.getRequest = getRequest;
Vue.config.productionTip=false
请求转发代理vue.config.js
//代理对象
let proxyObj = {};
//拦截http请求
proxyObj['/']={
//关闭websocket
ws:false,
//目标拦截地址
target:'http://localhost:8085',
//属性
changeOrigin:true,
//请求地址重写
pathRewrite:{
'^/':''
}
}
module.exports={
//开发环境
devServer:{
host:'localhost',
//服务本身信息
port:8081,
//代理就是上面的代理对象
proxy:proxyObj
}
}
Home页title制作 左边导航菜单制作
container容器
NavMenu导航菜单
Home.vue
<template>
<div>
<el-container>
<el-header class="homeHeader">
<div class="title">
人事管理系统
</div>
<!-- 手指效果 src为变量-->
<el-dropdown class="userInfo" @command="commandHandler">
<span class="el-dropdown-link">
<!-- 头像效果,src为变量-->
{{user.name}}<i><img :src="user.userface" alt=""></i>
</span>
<el-dropdown-menu slot="dropdown">
<!-- command为点击事件,点击回调-->
<el-dropdown-item command="userinfo">个人中心</el-dropdown-item>
<el-dropdown-item command="setting">设置</el-dropdown-item>
<el-dropdown-item command="logout" divided>注销登录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-header>
<!-- container容器,设置index.html的margin和padding-->
<el-container>
<el-aside width="200px">
<!-- 同一时间只打开一个菜单项-->
<!-- NavMenu导航菜单中,router属性是指是否使用vue-router的模式,启用该模式会在激活导航是以index作为path进行路由跳转-->
<el-menu router unique-opened>
<!-- <template v-if="!this.$router.options.routes.hidden">-->
<el-submenu :index="index+''" v-for="(item,index) in routes"
: key="index" >
<template slot="title" v-if="!item.hidden">
<i :class="item.iconCls" style="color: #409eff;margin-right: 5px"></i>
<span>{{ item.name }}</span>
</template>
<!-- <el-submenu index="1-4">-->
<!-- <template slot="title">选项4</template>-->
<el-menu-item :index="child.path" v-for="(child,indexj) in item.children" :key="indexj">{{child.name}}</el-menu-item>
<!-- <el-menu-item index="/test2">选项2</el-menu-item>-->
<!-- </el-submenu>-->
</el-submenu>
<!-- </template>-->
</el-menu>
</el-aside>
<el-container>
<el-main>
<el-breadcrumb separator-class="el-icon-arrow-right" v-if="this.$router.currentRoute.path!='/home'">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item >{{ this.$router.currentRoute.name }}</el-breadcrumb-item>
</el-breadcrumb>
<div class="homeWelcome" v-if="this.$router.currentRoute.path=='/home'">
欢迎来到人事管理系统
</div>
<router-view class="homeRouterView"/>
</el-main>
</el-container>
</el-container>
</el-container>
</div>
</template>
<script>
// import {initMenu} from "@/utils/menu";
export default {
name: "Home",
data(){
return{
//从store中获取菜单json,渲染成菜单
user:JSON.parse(window.sessionStorage.getItem("user"))
}
},
computed:{
routes(){
return this.$store.state.routes;
}
},
methods:{
// menuClick(index){
// this.$router.push(index);
// },
commandHandler(cmd){
// messagebox消息弹框
if(cmd === 'logout'){
this.$confirm('此操作将注销登陆, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 注销登录
this.getRequest("/logout");
//登录数据清空
window.sessionStorage.removeItem("user"
)
//跳转到登录页面
this.$store.commit('initRoutes',[])
this.$router.replace("/")
}).catch(() => {
this.$message({
type: 'info',
message: '已取消操作'
});
});
}
}
}
}
</script>
<style>
.homeRouterView{
margin-top: 10px;
}
.homeWelcome{
text-align: center;
font-size: 30px;
font-family: 黑体, cursive;
color: #409eff;
padding-top: 50px;
}
.homeHeader{
background-color: #409eff;
/*flex布局*/
display: flex;
align-items: center;
/*水平方向往两边走,空白的地方在中间*/
justify-content: space-between;
/*防止两边离得太近*/
padding: 0px 15px;
/*padding在框的里面*/
box-sizing: border-box;
}
.homeHeader .title{
font-size: 25px;
font-family: 黑体,cursive;
color: #fcfcfc;
}
.homeHeader .userInfo{
cursor: pointer;
}
.el-dropdown-link img{
width: 48px;
height: 48px;
border-radius: 24px;
margin-left: 8px;
}
.el-dropdown-link{
display: flex;
align-items: center;
}
</style>
动态加载菜单,从数据库中加载菜单。后端接口
@RestController
@RequestMapping("/system/config")
public class SystemConfigController {
@Autowired
MenuService menuService;
@GetMapping("/menu")
public List<Menu> getMenusByHrId(){
return menuService.getMenusByHrId();
}
}
id应该通过后端查询,前端传进来的数据时不可信的,可能会传到别人的菜单
封装一个menuService进行查询
package org.javagirl.vhr_project.service;
import org.javagirl.vhr_project.mapper.MenuMapper;
import org.javagirl.vhr_project.mapper.MenuRoleMapper;
import org.javagirl.vhr_project.model.Hr;
import org.javagirl.vhr_project.model.Menu;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Service
public class MenuService {
@Autowired
MenuMapper menuMapper;
@Autowired
MenuRoleMapper menuRoleMapper;
//获取id
public List<Menu> getMenusByHrId() {
return menuMapper.getMenusByHrId(((Hr)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId());
}
// @Cacheable 缓存,防止每次请求都查数据库
public List<Menu> getAllMenusWithRole(){
return menuMapper.getAllMenusWithRole();
}
public List<Menu> getAllMenus() {
return menuMapper.getAllMenus();
}
public List<Integer> getMidsByRid(Integer rid) {
return menuMapper.getMidsByRid(rid);
}
@Transactional
public boolean updateMenuRole(Integer rid, Integer[] mids) {
menuRoleMapper.deleteByRid(rid);
Integer result = menuRoleMapper.insertRecord(rid,mids);
return result==mids.length;
}
}
<select id="getMenusByHrId" resultMap="Menus2">
SELECT DISTINCT m1.*,m2.`id` AS id2,m2.`component` AS component2,m2.`enabled` AS enabled2,m2.`iconCls` AS iconCls2,m2.`keepAlive` AS keepAlive2,m2.`name` AS name2,m2.`parentId` AS parentId2,m2.`path` AS path2,m2.`requireAuth` AS requireAuth2 FROM menu m1,menu m2,hr_role hrr,menu_role mr WHERE m1.`id`=m2.`parentId`AND hrr.`hrid`=#{hrid} AND hrr.`rid`=mr.`rid`AND mr.`mid`=m2.`id` AND m2.`enabled`=TRUE ORDER BY m1.`id`,m2.`id`;
</select>
<resultMap id="Menus2" type="org.javagirl.vhr_project.model.Menu" extends="BaseResultMap">
<collection property="children" ofType="org.javagirl.vhr_project.model.Menu">
<id column="id2" property="id" jdbcType="INTEGER" />
<result column="path2" property="path" jdbcType="VARCHAR" />
<result column="component2" property="component" jdbcType="VARCHAR" />
<result column="name2" property="name" jdbcType="VARCHAR" />
<result column="iconCls2" property="iconCls" jdbcType="VARCHAR" />
<result column="parentId2" property="parentId" jdbcType="INTEGER" />
<result column="enabled2" property="enabled" jdbcType="BIT" />
<association property="meta" javaType="org.javagirl.vhr_project.model.Meta">
<result column="keepAlive2" property="keepAlive" jdbcType="BIT" />
<result column="requireAuth2" property="requireAuth" jdbcType="BIT" />
</association>
</collection>
</resultMap>
现在客户端中运行sql成功后再复制到对应的位置,以免出错
根据hr表获取hrid,hr_role表通过hrid获取rid,menu_role中通过rid获取mid,menu表中通过mid获取用户可以操作的menu
得到每个页面的父页面
菜单项接口介绍
菜单项数据加载成功之后,在前端有几个可以存放的地方:
- sessionStorage 不是很安全
- localStorage 不是很安全
- vuex 菜单数据在多个文件引用,放在menu.js。确保数据安全,所有都能访问
vuex 状态管理,数据调用,数据共享。
多个vue文件多次进行切换时使用keepAlive字段管理,但是可能会出现加载混乱的问题
vuex把数据放在公共的地方
vux是一个UI库
安装
npm install vuex
vue.js官网的状态管理中的内容
vuex是一个专为vue.js应用程序开发的状态管理模式,采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
/**
* 最开始的界面:index.js->main.js->App.vue->router.js
* 当用户注销登陆时,将localStorage中的数据清除
*/
export default new Vuex.Store({
state:{
routes:[]
},
mutations:{
initRoutes(state,data){
state.routes = data;
}
},
actions:{
}
})
在main.js中引入store文件
import Vue from 'vue'
import ElementUI from 'element-ui';
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip=false
Vue.use(ElementUI,{size:'small'});
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
menu.js
import {getRequest} from "@/utils/api";
// 浏览器刷新后保证菜单栏还在,可以使用路由导航守卫
export const initMenu=(router,store)=>{
//判断store中的数据是否存在,如果存在,说明这次跳转是正常的跳转,而不是用户按F5或者直接在地址栏输入某个地址进入的。否则去加载菜单。
if(store.state.routes.length>0){
return;
}
getRequest("/system/config/menu").then(data=>{
if(data){
//通过formatRoutes方法将服务器返回的json转为router需要的格式,转component,因为服务端返回的component事一个字符串,router中需要的事一个组件,在firmatRoutes方法中动态的加载需要的组件。
let fmtRoutes = formatRoutes(data);
//一方面将数据存入store中,另一方面利用路由中的addRoutes方法将它动态的添加到路由中
router.addRoutes(fmtRoutes);
store.commit('initRoutes',fmtRoutes);
}
})
}
export const formatRoutes=(routes)=>{
let fmRoutes=[];
routes.forEach(router=>{
//批量定义
let{
path,
component,
name,
meta,
iconCls,
children
}=router;
if(children && children instanceof Array){
children=formatRoutes(children);
}
let fmRouter={
path:path,
name:name,
iconCls:iconCls,
meta:meta,
children:children,
component(resolve) {
// if (component.startsWith("Home")) {
// require(['../views/' + component + '.vue'], resolve);
// } else if (component == "EmpBasic") {
// require(['../views/emp/' + component + '.vue'], resolve);
// } else if (component == "PerEmp") {
// require(['../views/per/' + component + '.vue'], resolve);
// }else if (component == "SalSobCfg") {
// require(['../views/sal/' + component + '.vue'], resolve);
// } else if (component == "SalSob") {
// require(['../views/sal/' + component + '.vue'], resolve);
// } else if (component == "SysHr") {
// require(['../views/sys/' + component + '.vue'], resolve);
// } else if (component == "SysBasic") {
// require(['../views/sys/' + component + '.vue'], resolve);
// }
if (component.startsWith("Home")) {
require(['../views/' + component + '.vue'], resolve);
} else if (component.startsWith("Emp")) {
require(['../views/emp/' + component + '.vue'], resolve);
} else if (component.startsWith("Per")) {
require(['../views/per/' + component + '.vue'], resolve);
} else if (component.startsWith("Sal")) {
require(['../views/sal/' + component + '.vue'], resolve);
} else if (component.startsWith("Sta")) {
require(['../views/sta/' + component + '.vue'], resolve);
} else if (component.startsWith("Sys")) {
require(['../views/sys/' + component + '.vue'], resolve);
}
}
}
fmRoutes.push(fmRouter);
})
return fmRoutes;
}
前端页面添加并完善菜单请求
views中创建菜单文件.vue
页面刷新,或按f5后菜单还在。
路由导航守卫,全局前置守卫
main.js
import Vue from 'vue'
import ElementUI from 'element-ui';
import App from './App.vue'
import router from './router'
import store from './store'
/**
* 将请求方法挂到vue上,后面需要发送网络请求时,不需要导入api
*/
//1.导入所有的请求方法
import {postRequest} from "@/utils/api";
import {postKeyValueRequest} from "@/utils/api";
import {putRequest} from "@/utils/api";
import {deleteRequest} from "@/utils/api";
import {getRequest} from "@/utils/api";
import {initMenu} from "@/utils/menu";
import 'font-awesome/css/font-awesome.min.css'
//2.将请求方法添加到vue.prototype上
Vue.prototype.postRequest = postRequest;
Vue.prototype.postKeyValueRequest = postKeyValueRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.deleteRequest = deleteRequest;
Vue.prototype.getRequest = getRequest;
Vue.config.productionTip=false
Vue.use(ElementUI,{size:'small'});
// 全局导航首页,页面跳转前监听
router.beforeEach((to, from, next) => {
//1. 如果要去的页面是登录页面,直接过
// 2. 如果不是登录页面,先从store中获取当前的登录状态。如果未登录,通过路由中的meta属性的requireAuth属性判断要去的页面是否需要登录,如果需要登录,调回登录页面,如果不需要登录,直接过。如果已经登陆了,先初始化菜单,再跳转
if(to.path === '/'){
next();
}else{
// 从sessionStorage中拿到用户登陆数据,判断用户是否登陆
if(window.sessionStorage.getItem("user")){
initMenu(router,store);
next();
}else{
// 没有登录
next('/?redirect='+to.path);
}
}
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Home.vue
computed:{
routes(){
return this.$store.state.routes;
}
},
<el-aside width="200px">
<!-- 同一时间只打开一个菜单项-->
<!-- NavMenu导航菜单中,router属性是指是否使用vue-router的模式,启用该模式会在激活导航是以index作为path进行路由跳转-->
<el-menu router unique-opened>
<!-- <template v-if="!this.$router.options.routes.hidden">-->
<el-submenu :index="index+''" v-for="(item,index) in routes"
: key="index" >
<template slot="title" v-if="!item.hidden">
<i :class="item.iconCls" style="color: #409eff;margin-right: 5px"></i>
<span>{{ item.name }}</span>
</template>
<!-- <el-submenu index="1-4">-->
<!-- <template slot="title">选项4</template>-->
<el-menu-item :index="child.path" v-for="(child,indexj) in item.children" :key="indexj">{{child.name}}</el-menu-item>
<!-- <el-menu-item index="/test2">选项2</el-menu-item>-->
<!-- </el-submenu>-->
</el-submenu>
<!-- </template>-->
</el-menu>
</el-aside>
面包屑作为导航线
前后端分离权限管理
前端跳转页面是为了提高用户体验
真正的权限校验在后端做,SSM框架建议使用shiro,SpringBoot+微服务,建议使用Spring Security
后端接口权限设计
用户发起HTTP请求,后端查询匹配的路径,查看当前用户是否具有该权限
一级菜单不需要用户权限
- 根据用户发送的url地址提取需要的角色,
- 判断当前用户是否具有相应角色
1.这个类的作用,主要是根据用户传来的请求地址,分析出请求需要的角色
CustomFilterInvocationSecurityMetadataSource
package org.javagirl.vhr_project.config;
/**
* 这个类的作用,主要是根据用户传来的请求地址,分析出请求需要的角色
* 获取该地址需要的用户角色
*/
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
@Autowired
MenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();
// 请求匹配,没匹配上的登录之后访问
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//获取请求地址
String requestUrl=((FilterInvocation) object).getRequestUrl();
//查询数据库中url pattern和role的对应关系
List<Menu> menus=menuService.getAllMenusWithRole();
for (Menu menu : menus) {
//提取当前的请求url,将请求和数据库查询出来的所有路径匹配规则进行匹配
if(antPathMatcher.match(menu.getUrl(),requestUrl)){
//获取该路径对应的角色
List<Role> roles = menu.getRoles();
String[] str=new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
str[i] = roles.get(i).getName();
}
return SecurityConfig.createList(str);
}
}
//ROLE_LOGIN标记,没匹配上的,都是登陆访问
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
MenuMapper
//开发中加缓存,使用redis或者@Cacheable
List<Menu> getAllMenusWithRole();
<resultMap id="BaseResultMap" type="org.javagirl.vhr_project.model.Menu" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="url" property="url" jdbcType="VARCHAR" />
<result column="path" property="path" jdbcType="VARCHAR" />
<result column="component" property="component" jdbcType="VARCHAR" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="iconCls" property="iconCls" jdbcType="VARCHAR" />
<result column="parentId" property="parentId" jdbcType="INTEGER" />
<result column="enabled" property="enabled" jdbcType="BIT" />
<association property="meta" javaType="org.javagirl.vhr_project.model.Meta">
<result column="keepAlive" property="keepAlive" jdbcType="BIT" />
<result column="requireAuth" property="requireAuth" jdbcType="BIT" />
</association>
</resultMap>
<resultMap id="MenuWithRole" type="org.javagirl.vhr_project.model.Menu" extends="BaseResultMap">
<collection property="roles" ofType="org.javagirl.vhr_project.model.Role">
<id column="rid" property="id"/>
<result column="rname" property="name"/>
<result column="rnameZh" property="nameZh"/>
</collection>
</resultMap>
<select id="getAllMenusWithRole" resultMap="MenuWithRole">
SELECT m.*,r.`id` AS rid,r.`name` AS rname,r.`nameZh` AS rnameZh FROM menu m,menu_role mr,role r WHERE m.`id`=mr.`mid` AND mr.`rid`=r.`id` ORDER BY m.`id`
</select>
-
判断当前哟用户是否具备角色
package org.javagirl.vhr_project.config; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import java.util.Collection; /** * 判断当前用户是否具有角色,请求是否通过 */ @Component public class CustomUrlDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { for (ConfigAttribute configAttribute : configAttributes) { //当前请求需要的权限 String needRole = configAttribute.getAttribute(); //ROLE_LOGIN标记,没有权限 if("ROLE_LOGIN".equals(needRole)){ if(authentication instanceof AnonymousAuthenticationToken){ throw new AccessDeniedException("尚未登录,请登录"); }else{ return; } } //当前用户具有的权限 Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); //查看当前用户的角色列表中是否具备需要的权限 //假设当前用户具备多个橘色,当前请求需要多个角色。 //只要用户包含一个请求角色就算授权成功 for (GrantedAuthority authority : authorities) { if(authority.getAuthority().equals(needRole)){ return; } } } throw new AccessDeniedException("权限不足,请联系管理员"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }
Hr类
private List<Role> roles; //获取当前用户所具有的角色 @Override //生成json时忽略该属性 @JsonIgnore public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(roles.size()); //roles属性描述当前用户的角色 for (Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; }
HrService
//在执行登陆的过程中,这个方法根据用户名取查找用户
//如果用户不存在,抛出异常,否则返回Hr
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Hr hr = hrMapper.loadUserByUsername(username);
if(hr == null){
throw new UsernameNotFoundException("用户名不存在");
}
hr.setRoles(hrMapper.getHrRolesById(hr.getId()));
return hr;
}
<select id="getHrRolesById" resultType="org.javagirl.vhr_project.model.Role"
parameterType="java.lang.Integer">
SELECT r.* FROM role r,hr_role hrr WHERE hrr.`rid`=r.`id` AND hrr.`hrid`=#{id}
</select>
基础信息设计
<template>
<el-tabs v-model="activeName" type="card">
<el-tab-pane label="部门管理" name="depmanager"><DepManager></DepManager></el-tab-pane>
<el-tab-pane label="职位管理" name="posmanager"><PosManager></PosManager></el-tab-pane>
<el-tab-pane label="职称管理" name="joblevelmanager"><JobLevelManager></JobLevelManager></el-tab-pane>
<el-tab-pane label="权限组" name="permissmanager"><PermissManager></PermissManager></el-tab-pane>
</el-tabs>
</template>
<script>
import DepManager from "@/components/sys/basic/DepManager";
import PosManager from "@/components/sys/basic/PosManager";
import JobLevelManager from "@/components/sys/basic/JobLevelManager";
import PermissManager from "@/components/sys/basic/PermissManager";
export default {
name: "SysBasic",
data(){
return{
activeName:'depmanager'
}
},
components:{
//key:value形式,key value相同时,可以省略key
'DepManager':DepManager,
PosManager,
JobLevelManager,
PermissManager
}
}
</script>
<style scoped>
</style>
职位管理前端页面
<!--template渲染时会消失,div样式显示不清楚,只能有一个div-->
<!--职位管理-->
<template>
<div>
<div>
<el-input
size="small"
class="addPosInput"
placeholder="添加职位..."
prefix-icon="el-icon-plus"
@keydown.enter.native="addPosition"
v-model="pos.name">
</el-input>
<el-button icon="el-icon-plus" size="small" type="primary" @click="addPosition">添加</el-button>
</div>
<div class="posManagerMain">
<el-table
:data="positions"
@selection-change="handleSelectionChange"
size="small"
border
stripe
style="width: 70%">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column
prop="id"
label="编号"
width="55">
</el-table-column>
<el-table-column
prop="name"
label="职位名称"
width="180">
</el-table-column>
<el-table-column
prop="createDate"
width="120"
label="创建时间">
</el-table-column>
<el-table-column
prop="enabled"
width="100"
label="是否启用">
<template slot-scope="scope">
<el-tag v-if="scope.row.enabled" type="success">已启用</el-tag>
<el-tag v-else type="danger">未启用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="showEditView(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-button @click="deleteMany" type="danger" size="small" style="margin-top: 8px;" :disabled="multipleSelection.length===0">批量删除</el-button>
</div>
<el-dialog
title="修改职位名称"
:visible.sync="dialogVisible"
width="30%"
>
<div>
<div>
<el-tag>职位名称</el-tag>
<el-input class="updatePosInput" size="small" v-model="updatePos.name"></el-input>
</div>
<div>
<el-switch
v-model="updatePos.enabled"
active-text="启用"
inactive-text="禁用">
</el-switch>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogVisible = false">取 消</el-button>
<el-button size="small" type="primary" @click="doUpdate">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {deleteRequest} from "@/utils/api";
export default {
name: "PosManager",
data(){
return {
pos:{
name:''
},
positions: [],
updatePos:{
name:'',
enabled:false
},
multipleSelection:[],
dialogVisible:false
}
},
// 组件初始化
mounted() {
this.initPositions();
},
methods:{
deleteMany(){
this.$confirm('此操作将永久删除【'+this.multipleSelection.length+'】条记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let ids='?';
this.multipleSelection.forEach(item=>{
ids+='ids='+item.id+'&';
})
this.deleteRequest("/system/basic/pos/"+ids).then(resp=>{
if(resp){
this.initPositions();
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
showEditView(index,data){
this.dialogVisible=true;
//数据拷贝,防止修改取消时出错
Object.assign(this.updatePos,data);
// this.updatePos=data;
},
doUpdate(){
this.putRequest("/system/basic/pos/",this.updatePos).then(resp=>{
if(resp){
this.initPositions();
this.updatePos.name='';
this.dialogVisible=false;
}
})
},
// 删除职位
handleDelete(index,data){
this.$confirm('此操作将永久删除['+data.name+']职位, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteRequest("/system/basic/pos/"+data.id).then(resp=> {
if (resp) {
this.initPositions();
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
// 添加职位
addPosition(){
if(this.pos.name){
this.postRequest("/system/basic/pos/",this.pos).then(resp=>{
if(resp){
this.initPositions();
this.pos.name='';
}
})
}else{
this.$message({
message: '职位名称不可以为空'
});
}
},
// 初始化数据
initPositions(){
this.getRequest("/system/basic/pos/").then(resp=>{
if(resp){
this.positions=resp;
}
})
}
}
}
</script>
<style>
.updatePosInput{
width: 200px;
margin-left: 8px;
}
.addPosInput{
width: 300px;
margin-right: 8px
}
.posManagerMain{
margin-top: 10px;
}
</style>
职位管理后端接口
package org.javagirl.vhr_project.controller.system.basic;
import org.javagirl.vhr_project.model.Position;
import org.javagirl.vhr_project.model.RespBean;
import org.javagirl.vhr_project.service.PositionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.annotation.RequestScope;
import java.util.List;
@RestController
@RequestMapping("/system/basic/pos")
public class PositionController {
@Autowired
PositionService positionService;
@GetMapping("/")
public List<Position> getAllPositions() {
return positionService.getAllPositions();
}
@PostMapping("/")
public RespBean addPosition(@RequestBody Position position){
if(positionService.addPosition(position) == 1){
return RespBean.ok("添加成功");
}
return RespBean.error("添加失败");
}
@PutMapping("/")
public RespBean updatePositions(@RequestBody Position position){
if(positionService.updatePositions(position)==1){
return RespBean.ok("更新成功");
}
return RespBean.error("更新失败");
}
@DeleteMapping("/{id}")
public RespBean deletePosition(@PathVariable Integer id){
if(positionService.deletePositionById(id)==1){
return RespBean.ok("删除成功");
}
return RespBean.error("删除失败");
}
@DeleteMapping("/")
public RespBean deletePositionByIds(Integer[] ids){
if(positionService.deletePositionByIds(ids)==ids.length){
return RespBean.ok("删除成功");
}
return RespBean.ok("删除失败");
}
}
Service
package org.javagirl.vhr_project.service;
import org.javagirl.vhr_project.mapper.PositionMapper;
import org.javagirl.vhr_project.model.Position;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class PositionService {
@Autowired
PositionMapper positionMapper;
public List<Position> getAllPositions() {
return positionMapper.getAllPositions();
}
public Integer addPosition(Position position) {
position.setEnabled(true);
position.setCreateDate(new Date());
return positionMapper.insertSelective(position);
}
public Integer updatePositions(Position position) {
return positionMapper.updateByPrimaryKeySelective(position);
}
public Integer deletePositionById(Integer id) {
return positionMapper.deleteByPrimaryKey(id);
}
public Integer deletePositionByIds(Integer[] ids) {
return positionMapper.deletePositionsByIds(ids);
}
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="org.javagirl.vhr_project.mapper.PositionMapper" >
<resultMap id="BaseResultMap" type="org.javagirl.vhr_project.model.Position" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="createDate" property="createDate" jdbcType="TIMESTAMP" />
<result column="enabled" property="enabled" jdbcType="BIT" />
</resultMap>
<sql id="Base_Column_List" >
id, name, createDate, enabled
</sql>
<select id="getAllPositions" resultMap="BaseResultMap">
select * from position
</select>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from position
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from position
where id = #{id,jdbcType=INTEGER}
</delete>
<delete id="deletePositionsByIds">
delete from position where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id}</foreach>
</delete>
<insert id="insert" parameterType="org.javagirl.vhr_project.model.Position" >
insert into position (id, name, createDate,
enabled)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{createDate,jdbcType=TIMESTAMP},
#{enabled,jdbcType=BIT})
</insert>
<insert id="insertSelective" parameterType="org.javagirl.vhr_project.model.Position" >
insert into position
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="id != null" >
id,
</if>
<if test="name != null" >
name,
</if>
<if test="createDate != null" >
createDate,
</if>
<if test="enabled != null" >
enabled,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="id != null" >
#{id,jdbcType=INTEGER},
</if>
<if test="name != null" >
#{name,jdbcType=VARCHAR},
</if>
<if test="createDate != null" >
#{createDate,jdbcType=TIMESTAMP},
</if>
<if test="enabled != null" >
#{enabled,jdbcType=BIT},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="org.javagirl.vhr_project.model.Position" >
update position
<set >
<if test="name != null" >
name = #{name,jdbcType=VARCHAR},
</if>
<if test="createDate != null" >
createDate = #{createDate,jdbcType=TIMESTAMP},
</if>
<if test="enabled != null" >
enabled = #{enabled,jdbcType=BIT},
</if>
</set>
where id = #{id,jdbcType=INTEGER}
</update>
<update id="updateByPrimaryKey" parameterType="org.javagirl.vhr_project.model.Position" >
update position
set name = #{name,jdbcType=VARCHAR},
createDate = #{createDate,jdbcType=TIMESTAMP},
enabled = #{enabled,jdbcType=BIT}
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>