添加springboot-shiro

This commit is contained in:
yidao620
2018-02-27 17:43:36 +08:00
parent 2cf3ac80b5
commit f555cb345f
67 changed files with 11584 additions and 1 deletions

View File

@ -0,0 +1,12 @@
package com.xncoding.pos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,249 @@
package com.xncoding.pos.common.dao.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.activerecord.Model;
import java.io.Serializable;
/**
* 后台管理用户表
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
@TableName(value = "t_manager")
public class Manager extends Model<Manager> {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value="id", type= IdType.AUTO)
private Integer id;
/**
* 账号
*/
private String username;
/**
* 名字
*/
private String name;
/**
* 密码
*/
private String password;
/**
* md5密码盐
*/
private String salt;
/**
* 联系电话
*/
private String phone;
/**
* 备注
*/
private String tips;
/**
* 状态 1:正常 2:禁用
*/
private Integer state;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
/**
* 获取 主键ID.
*
* @return 主键ID.
*/
public Integer getId() {
return id;
}
/**
* 设置 主键ID.
*
* @param id 主键ID.
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取 账号.
*
* @return 账号.
*/
public String getUsername() {
return username;
}
/**
* 设置 账号.
*
* @param username 账号.
*/
public void setUsername(String username) {
this.username = username;
}
/**
* 获取 名字.
*
* @return 名字.
*/
public String getName() {
return name;
}
/**
* 设置 名字.
*
* @param name 名字.
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取 密码.
*
* @return 密码.
*/
public String getPassword() {
return password;
}
/**
* 设置 密码.
*
* @param password 密码.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* 获取 md5密码盐.
*
* @return md5密码盐.
*/
public String getSalt() {
return salt;
}
/**
* 设置 md5密码盐.
*
* @param salt md5密码盐.
*/
public void setSalt(String salt) {
this.salt = salt;
}
/**
* 获取 联系电话.
*
* @return 联系电话.
*/
public String getPhone() {
return phone;
}
/**
* 设置 联系电话.
*
* @param phone 联系电话.
*/
public void setPhone(String phone) {
this.phone = phone;
}
/**
* 获取 备注.
*
* @return 备注.
*/
public String getTips() {
return tips;
}
/**
* 设置 备注.
*
* @param tips 备注.
*/
public void setTips(String tips) {
this.tips = tips;
}
/**
* 获取 状态 1:正常 2:禁用.
*
* @return 状态 1:正常 2:禁用.
*/
public Integer getState() {
return state;
}
/**
* 设置 状态 1:正常 2:禁用.
*
* @param state 状态 1:正常 2:禁用.
*/
public void setState(Integer state) {
this.state = state;
}
/**
* 获取 创建时间.
*
* @return 创建时间.
*/
public Date getCreatedTime() {
return createdTime;
}
/**
* 设置 创建时间.
*
* @param createdTime 创建时间.
*/
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
/**
* 获取 更新时间.
*
* @return 更新时间.
*/
public Date getUpdatedTime() {
return updatedTime;
}
/**
* 设置 更新时间.
*
* @param updatedTime 更新时间.
*/
public void setUpdatedTime(Date updatedTime) {
this.updatedTime = updatedTime;
}
@Override
protected Serializable pkVal() {
return this.id;
}
}

View File

@ -0,0 +1,139 @@
package com.xncoding.pos.common.dao.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.activerecord.Model;
import java.io.Serializable;
/**
* 用户角色关联表
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
@TableName(value = "t_manager_role")
public class ManagerRole extends Model<ManagerRole> {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value="id", type= IdType.AUTO)
private Integer id;
/**
* 管理用户ID
*/
private Integer managerId;
/**
* 角色ID
*/
private Integer roleId;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
/**
* 获取 主键ID.
*
* @return 主键ID.
*/
public Integer getId() {
return id;
}
/**
* 设置 主键ID.
*
* @param id 主键ID.
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取 管理用户ID.
*
* @return 管理用户ID.
*/
public Integer getManagerId() {
return managerId;
}
/**
* 设置 管理用户ID.
*
* @param managerId 管理用户ID.
*/
public void setManagerId(Integer managerId) {
this.managerId = managerId;
}
/**
* 获取 角色ID.
*
* @return 角色ID.
*/
public Integer getRoleId() {
return roleId;
}
/**
* 设置 角色ID.
*
* @param roleId 角色ID.
*/
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
/**
* 获取 创建时间.
*
* @return 创建时间.
*/
public Date getCreatedTime() {
return createdTime;
}
/**
* 设置 创建时间.
*
* @param createdTime 创建时间.
*/
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
/**
* 获取 更新时间.
*
* @return 更新时间.
*/
public Date getUpdatedTime() {
return updatedTime;
}
/**
* 设置 更新时间.
*
* @param updatedTime 更新时间.
*/
public void setUpdatedTime(Date updatedTime) {
this.updatedTime = updatedTime;
}
@Override
protected Serializable pkVal() {
return this.id;
}
}

View File

@ -0,0 +1,139 @@
package com.xncoding.pos.common.dao.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.activerecord.Model;
import java.io.Serializable;
/**
* 权限表
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
@TableName(value = "t_permission")
public class Permission extends Model<Permission> {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value="id", type= IdType.AUTO)
private Integer id;
/**
* 权限名称
*/
private String permission;
/**
* 权限说明
*/
private String description;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
/**
* 获取 主键ID.
*
* @return 主键ID.
*/
public Integer getId() {
return id;
}
/**
* 设置 主键ID.
*
* @param id 主键ID.
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取 权限名称.
*
* @return 权限名称.
*/
public String getPermission() {
return permission;
}
/**
* 设置 权限名称.
*
* @param permission 权限名称.
*/
public void setPermission(String permission) {
this.permission = permission;
}
/**
* 获取 权限说明.
*
* @return 权限说明.
*/
public String getDescription() {
return description;
}
/**
* 设置 权限说明.
*
* @param description 权限说明.
*/
public void setDescription(String description) {
this.description = description;
}
/**
* 获取 创建时间.
*
* @return 创建时间.
*/
public Date getCreatedTime() {
return createdTime;
}
/**
* 设置 创建时间.
*
* @param createdTime 创建时间.
*/
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
/**
* 获取 更新时间.
*
* @return 更新时间.
*/
public Date getUpdatedTime() {
return updatedTime;
}
/**
* 设置 更新时间.
*
* @param updatedTime 更新时间.
*/
public void setUpdatedTime(Date updatedTime) {
this.updatedTime = updatedTime;
}
@Override
protected Serializable pkVal() {
return this.id;
}
}

View File

@ -0,0 +1,139 @@
package com.xncoding.pos.common.dao.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.activerecord.Model;
import java.io.Serializable;
/**
* 角色表
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
@TableName(value = "t_role")
public class Role extends Model<Role> {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value="id", type= IdType.AUTO)
private Integer id;
/**
* 角色名称
*/
private String role;
/**
* 角色说明
*/
private String description;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
/**
* 获取 主键ID.
*
* @return 主键ID.
*/
public Integer getId() {
return id;
}
/**
* 设置 主键ID.
*
* @param id 主键ID.
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取 角色名称.
*
* @return 角色名称.
*/
public String getRole() {
return role;
}
/**
* 设置 角色名称.
*
* @param role 角色名称.
*/
public void setRole(String role) {
this.role = role;
}
/**
* 获取 角色说明.
*
* @return 角色说明.
*/
public String getDescription() {
return description;
}
/**
* 设置 角色说明.
*
* @param description 角色说明.
*/
public void setDescription(String description) {
this.description = description;
}
/**
* 获取 创建时间.
*
* @return 创建时间.
*/
public Date getCreatedTime() {
return createdTime;
}
/**
* 设置 创建时间.
*
* @param createdTime 创建时间.
*/
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
/**
* 获取 更新时间.
*
* @return 更新时间.
*/
public Date getUpdatedTime() {
return updatedTime;
}
/**
* 设置 更新时间.
*
* @param updatedTime 更新时间.
*/
public void setUpdatedTime(Date updatedTime) {
this.updatedTime = updatedTime;
}
@Override
protected Serializable pkVal() {
return this.id;
}
}

View File

@ -0,0 +1,139 @@
package com.xncoding.pos.common.dao.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.activerecord.Model;
import java.io.Serializable;
/**
* 角色权限关联表
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
@TableName(value = "t_role_permission")
public class RolePermission extends Model<RolePermission> {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value="id", type= IdType.AUTO)
private Integer id;
/**
* 角色ID
*/
private Integer roleId;
/**
* 权限ID
*/
private Integer permissionId;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
/**
* 获取 主键ID.
*
* @return 主键ID.
*/
public Integer getId() {
return id;
}
/**
* 设置 主键ID.
*
* @param id 主键ID.
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取 角色ID.
*
* @return 角色ID.
*/
public Integer getRoleId() {
return roleId;
}
/**
* 设置 角色ID.
*
* @param roleId 角色ID.
*/
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
/**
* 获取 权限ID.
*
* @return 权限ID.
*/
public Integer getPermissionId() {
return permissionId;
}
/**
* 设置 权限ID.
*
* @param permissionId 权限ID.
*/
public void setPermissionId(Integer permissionId) {
this.permissionId = permissionId;
}
/**
* 获取 创建时间.
*
* @return 创建时间.
*/
public Date getCreatedTime() {
return createdTime;
}
/**
* 设置 创建时间.
*
* @param createdTime 创建时间.
*/
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
/**
* 获取 更新时间.
*
* @return 更新时间.
*/
public Date getUpdatedTime() {
return updatedTime;
}
/**
* 设置 更新时间.
*
* @param updatedTime 更新时间.
*/
public void setUpdatedTime(Date updatedTime) {
this.updatedTime = updatedTime;
}
@Override
protected Serializable pkVal() {
return this.id;
}
}

View File

@ -0,0 +1,15 @@
package com.xncoding.pos.common.dao.repository;
import com.xncoding.pos.common.dao.entity.Manager;
import com.baomidou.mybatisplus.mapper.BaseMapper;
/**
* 后台管理用户表 Mapper
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
public interface ManagerMapper extends BaseMapper<Manager> {
}

View File

@ -0,0 +1,15 @@
package com.xncoding.pos.common.dao.repository;
import com.xncoding.pos.common.dao.entity.ManagerRole;
import com.baomidou.mybatisplus.mapper.BaseMapper;
/**
* 用户角色关联表 Mapper
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
public interface ManagerRoleMapper extends BaseMapper<ManagerRole> {
}

View File

@ -0,0 +1,15 @@
package com.xncoding.pos.common.dao.repository;
import com.xncoding.pos.common.dao.entity.Permission;
import com.baomidou.mybatisplus.mapper.BaseMapper;
/**
* 权限表 Mapper
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
public interface PermissionMapper extends BaseMapper<Permission> {
}

View File

@ -0,0 +1,15 @@
package com.xncoding.pos.common.dao.repository;
import com.xncoding.pos.common.dao.entity.Role;
import com.baomidou.mybatisplus.mapper.BaseMapper;
/**
* 角色表 Mapper
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
public interface RoleMapper extends BaseMapper<Role> {
}

View File

@ -0,0 +1,15 @@
package com.xncoding.pos.common.dao.repository;
import com.xncoding.pos.common.dao.entity.RolePermission;
import com.baomidou.mybatisplus.mapper.BaseMapper;
/**
* 角色权限关联表 Mapper
*
* @author 熊能
* @version 1.0
* @since 2018/01/02
*/
public interface RolePermissionMapper extends BaseMapper<RolePermission> {
}

View File

@ -0,0 +1,47 @@
package com.xncoding.pos.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.xncoding.pos.config.properties.DruidProperties;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
/**
* MybatisPlus配置
*
* @author xiongneng
* @since 2017/5/20 21:58
*/
@Configuration
@EnableTransactionManagement(order = 2)
@MapperScan(basePackages = {
"com.xncoding.pos.common.dao.repository",
"com.xncoding.pos.dao.repository"})
public class MybatisPlusConfig {
@Resource
private DruidProperties druidProperties;
/**
* 单数据源连接池配置
*/
@Bean
public DruidDataSource singleDatasource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
return dataSource;
}
/**
* mybatis-plus分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}

View File

@ -0,0 +1,277 @@
package com.xncoding.pos.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.servlet.KaptchaServlet;
import com.xncoding.pos.shiro.KaptchaFilter;
import com.xncoding.pos.shiro.MyExceptionResolver;
import com.xncoding.pos.shiro.MyShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Description : Apache Shiro 核心通过 Filter 来实现就好像SpringMvc 通过DispachServlet 来主控制一样。
* 既然是使用 Filter 一般也就能猜到是通过URL规则来进行过滤和权限校验所以我们需要定义一系列关于URL的规则和访问权限。
*/
@Configuration
@Order(1)
public class ShiroConfig {
//配置kaptcha图片验证码框架提供的Servlet,,这是个坑,很多人忘记注册(注意)
@Bean
public ServletRegistrationBean kaptchaServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new KaptchaServlet(), "/kaptcha.jpg");
servlet.addInitParameter(Constants.KAPTCHA_SESSION_CONFIG_KEY, Constants.KAPTCHA_SESSION_KEY);//session key
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "50");//字体大小
servlet.addInitParameter(Constants.KAPTCHA_BORDER, "no");
servlet.addInitParameter(Constants.KAPTCHA_BORDER_COLOR, "105,179,90");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "45");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋体,楷体,微软雅黑");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
servlet.addInitParameter(Constants.KAPTCHA_IMAGE_WIDTH, "125");
servlet.addInitParameter(Constants.KAPTCHA_IMAGE_HEIGHT, "60");
//可以设置很多属性,具体看com.google.code.kaptcha.Constants
// kaptcha.border 是否有边框 默认为true 我们可以自己设置yesno
// kaptcha.border.color 边框颜色 默认为Color.BLACK
// kaptcha.border.thickness 边框粗细度 默认为1
// kaptcha.producer.impl 验证码生成器 默认为DefaultKaptcha
// kaptcha.textproducer.impl 验证码文本生成器 默认为DefaultTextCreator
// kaptcha.textproducer.char.string 验证码文本字符内容范围 默认为abcde2345678gfynmnpwx
// kaptcha.textproducer.char.length 验证码文本字符长度 默认为5
// kaptcha.textproducer.font.names 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
// kaptcha.textproducer.font.size 验证码文本字符大小 默认为40
// kaptcha.textproducer.font.color 验证码文本字符颜色 默认为Color.BLACK
// kaptcha.textproducer.char.space 验证码文本字符间距 默认为2
// kaptcha.noise.impl 验证码噪点生成对象 默认为DefaultNoise
// kaptcha.noise.color 验证码噪点颜色 默认为Color.BLACK
// kaptcha.obscurificator.impl 验证码样式引擎 默认为WaterRipple
// kaptcha.word.impl 验证码文本字符渲染 默认为DefaultWordRenderer
// kaptcha.background.impl 验证码背景生成器 默认为DefaultBackground
// kaptcha.background.clear.from 验证码背景颜色渐进 默认为Color.LIGHT_GRAY
// kaptcha.background.clear.to 验证码背景颜色渐进 默认为Color.WHITE
// kaptcha.image.width 验证码图片宽度 默认为200
// kaptcha.image.height 验证码图片高度 默认为50
return servlet;
}
//注入异常处理类
@Bean
public MyExceptionResolver myExceptionResolver() {
return new MyExceptionResolver();
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意单独一个ShiroFilterFactoryBean配置是或报错的以为在
* 初始化ShiroFilterFactoryBean的时候需要注入SecurityManager Filter Chain定义说明
* 1、一个URL可以配置多个Filter使用逗号分隔
* 2、当设置多个过滤器时全部验证通过才视为通过
* 3、部分过滤器可指定参数如permsroles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//验证码过滤器
Map<String, Filter> filtersMap = shiroFilterFactoryBean.getFilters();
KaptchaFilter kaptchaFilter = new KaptchaFilter();
filtersMap.put("kaptchaFilter", kaptchaFilter);
//实现自己规则roles,这是为了实现or的效果
//RoleFilter roleFilter = new RoleFilter();
//filtersMap.put("roles", roleFilter);
shiroFilterFactoryBean.setFilters(filtersMap);
// 拦截器
//rest比如/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[usermethod] ,其中method为postgetdelete等。
//port比如/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal//serverName8081?queryString,其中schmal是协议http或https等serverName是你访问的host,8081是url配置里port的端口queryString是你访问的url里的后面的参数。
//perms比如/admins/user/**=perms[useradd*],perms参数可以写多个多个时必须加上引号并且参数之间用逗号分割比如/admins/user/**=perms["useradd*,usermodify*"]当有多个参数时必须每个参数都通过才通过想当于isPermitedAll()方法。
//roles比如/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,比如/admins/user/**=roles["admin,guest"],每个参数通过才算通过相当于hasAllRoles()方法。//要实现or的效果看http://zgzty.blog.163.com/blog/static/83831226201302983358670/
//anon比如/admins/**=anon 没有参数,表示可以匿名使用。
//authc比如/admins/user/**=authc表示需要认证才能使用没有参数
//authcBasic比如/admins/user/**=authcBasic没有参数表示httpBasic认证
//ssl比如/admins/user/**=ssl没有参数表示安全的url请求协议为https
//user比如/admins/user/**=user没有参数表示必须存在用户当登入操作时不做检查
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//配置记住我或认证通过可以访问的地址
filterChainDefinitionMap.put("/index", "user");
filterChainDefinitionMap.put("/", "user");
filterChainDefinitionMap.put("/login", "kaptchaFilter");
// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//这段是配合 actuator框架使用的配置相应的角色才能访问
// filterChainDefinitionMap.put("/health", "roles[aix]");//服务器健康状况页面
// filterChainDefinitionMap.put("/info", "roles[aix]");//服务器信息页面
// filterChainDefinitionMap.put("/env", "roles[aix]");//应用程序的环境变量
// filterChainDefinitionMap.put("/metrics", "roles[aix]");
// filterChainDefinitionMap.put("/configprops", "roles[aix]");
//开放的静态资源
filterChainDefinitionMap.put("/favicon.ico", "anon");//网站图标
filterChainDefinitionMap.put("/static/**", "anon");//配置static文件下资源能被访问的这是个例子
filterChainDefinitionMap.put("/kaptcha.jpg", "anon");//图片验证码(kaptcha框架)
filterChainDefinitionMap.put("/api/v1/**", "anon");//API接口
// swagger接口文档
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/doc.html", "anon");
// 其他的
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面,不生效(详情原因看MyExceptionResolver)
shiroFilterFactoryBean.setUnauthorizedUrl("/errorView/403_error.html");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
//注入缓存管理器
securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;
//注入记住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 凭证匹配器 由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码; @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);// 散列的次数比如散列两次相当于md5(md5(""));
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);//表示是否存储散列后的密码为16进制需要和生成密码时的一样默认是base64
return hashedCredentialsMatcher;
}
/**
* 开启shiro aop注解支持. 使用代理方式; 所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* shiro缓存管理器;
* 需要注入对应的其它的实体类中:
* 1、安全管理器securityManager
* 可见securityManager是整个shiro的核心
*
* @return
*/
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
/**
* cookie对象;
*
* @return
*/
@Bean
public SimpleCookie rememberMeCookie() {
//System.out.println("ShiroConfiguration.rememberMeCookie()");
//这个参数是cookie的名称对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
/**
* cookie管理对象;
*
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
//System.out.println("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
@Bean(name = "sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(18000000);
// url中是否显示session Id
sessionManager.setSessionIdUrlRewritingEnabled(false);
// 删除失效的session
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionValidationInterval(18000000);
sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
//设置SessionIdCookie 导致认证不成功不从新设置新的cookie,从sessionManager获取sessionIdCookie
//sessionManager.setSessionIdCookie(simpleIdCookie());
sessionManager.getSessionIdCookie().setName("session-z-id");
sessionManager.getSessionIdCookie().setPath("/");
sessionManager.getSessionIdCookie().setMaxAge(60 * 60 * 24 * 7);
return sessionManager;
}
@Bean(name = "sessionValidationScheduler")
public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
scheduler.setInterval(900000);
return scheduler;
}
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}

View File

@ -0,0 +1,249 @@
package com.xncoding.pos.config.properties;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.JdbcConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
/**
* <p>数据库数据源配置</p>
* <p>说明:这个类中包含了许多默认配置,若这些配置符合您的情况,您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可</p>
*
* @author xiongneng
* @since 2017-05-21 11:18
*/
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
private String url;
private String username;
private String password;
private String driverClassName = "com.mysql.cj.jdbc.Driver";
private Integer initialSize = 10;
private Integer minIdle = 3;
private Integer maxActive = 60;
private Integer maxWait = 60000;
private Boolean removeAbandoned = true;
private Integer removeAbandonedTimeout = 180;
private Integer timeBetweenEvictionRunsMillis = 60000;
private Integer minEvictableIdleTimeMillis = 300000;
private String validationQuery = "SELECT 'x'";
private Boolean testWhileIdle = true;
private Boolean testOnBorrow = false;
private Boolean testOnReturn = false;
private Boolean poolPreparedStatements = true;
private Integer maxPoolPreparedStatementPerConnectionSize = 50;
private String filters = "stat";
public void config(DruidDataSource dataSource) {
dataSource.setDbType(JdbcConstants.MYSQL);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
dataSource.setInitialSize(initialSize); // 定义初始连接数
dataSource.setMinIdle(minIdle); // 最小空闲
dataSource.setMaxActive(maxActive); // 定义最大连接数
dataSource.setMaxWait(maxWait); // 获取连接等待超时的时间
dataSource.setRemoveAbandoned(removeAbandoned); // 超过时间限制是否回收
dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout); // 超过时间限制多长
// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 配置一个连接在池中最小生存的时间,单位是毫秒
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
// 用来检测连接是否有效的sql要求是一个查询语句
dataSource.setValidationQuery(validationQuery);
// 申请连接的时候检测
dataSource.setTestWhileIdle(testWhileIdle);
// 申请连接时执行validationQuery检测连接是否有效配置为true会降低性能
dataSource.setTestOnBorrow(testOnBorrow);
// 归还连接时执行validationQuery检测连接是否有效配置为true会降低性能
dataSource.setTestOnReturn(testOnReturn);
// 打开PSCache并且指定每个连接上PSCache的大小
dataSource.setPoolPreparedStatements(poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
// 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
// 监控统计用的filter:stat
// 日志用的filter:log4j
// 防御SQL注入的filter:wall
try {
dataSource.setFilters(filters);
} catch (SQLException e) {
e.printStackTrace();
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Integer getInitialSize() {
return initialSize;
}
public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Integer getMaxActive() {
return maxActive;
}
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
public Integer getMaxWait() {
return maxWait;
}
public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
}
public Integer getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public Integer getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public Boolean getTestWhileIdle() {
return testWhileIdle;
}
public void setTestWhileIdle(Boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
public Boolean getTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(Boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public Boolean getTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(Boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
public Boolean getPoolPreparedStatements() {
return poolPreparedStatements;
}
public void setPoolPreparedStatements(Boolean poolPreparedStatements) {
this.poolPreparedStatements = poolPreparedStatements;
}
public Integer getMaxPoolPreparedStatementPerConnectionSize() {
return maxPoolPreparedStatementPerConnectionSize;
}
public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) {
this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
}
public String getFilters() {
return filters;
}
public void setFilters(String filters) {
this.filters = filters;
}
public Boolean getRemoveAbandoned() {
return removeAbandoned;
}
public void setRemoveAbandoned(Boolean removeAbandoned) {
this.removeAbandoned = removeAbandoned;
}
public Integer getRemoveAbandonedTimeout() {
return removeAbandonedTimeout;
}
public void setRemoveAbandonedTimeout(Integer removeAbandonedTimeout) {
this.removeAbandonedTimeout = removeAbandonedTimeout;
}
}

View File

@ -0,0 +1,148 @@
package com.xncoding.pos.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 本项目自定义配置
*
* @author xiongneng
* @since 2018/01/06 21:09
*/
@Component
@ConfigurationProperties(prefix = "xncoding")
public class MyProperties {
/**
* excel模板文件路径
*/
private String excelPath = "";
/**
* 文件保存路径
*/
private String filesPath = "";
/**
* 图片保存路径
*/
private String picsPath = "";
/**
* 图片访问URL前缀
*/
private String picsUrlPrefix = "";
/**
* 文件访问URL前缀
*/
private String filesUrlPrefix = "";
/**
* POS API接口前缀
*/
private String posapiUrlPrefix = "";
/**
* 是否验证码
*/
private Boolean kaptchaOpen = false;
/**
* 是否开启Swaggr
*/
private Boolean swaggerOpen = false;
/**
* session 失效时间默认为30分钟 单位:秒)
*/
private Integer sessionInvalidateTime = 30 * 60;
/**
* session 验证失效时间默认为15分钟 单位:秒)
*/
private Integer sessionValidationInterval = 15 * 60;
/**
* 机具心跳报告超时时间 单位:分钟
*/
private Integer heartbeatTimeout;
public String getExcelPath() {
return excelPath;
}
public void setExcelPath(String excelPath) {
this.excelPath = excelPath;
}
public String getPicsUrlPrefix() {
return picsUrlPrefix;
}
public void setPicsUrlPrefix(String picsUrlPrefix) {
this.picsUrlPrefix = picsUrlPrefix;
}
public Boolean getKaptchaOpen() {
return kaptchaOpen;
}
public void setKaptchaOpen(Boolean kaptchaOpen) {
this.kaptchaOpen = kaptchaOpen;
}
public Boolean getSwaggerOpen() {
return swaggerOpen;
}
public void setSwaggerOpen(Boolean swaggerOpen) {
this.swaggerOpen = swaggerOpen;
}
public Integer getSessionInvalidateTime() {
return sessionInvalidateTime;
}
public void setSessionInvalidateTime(Integer sessionInvalidateTime) {
this.sessionInvalidateTime = sessionInvalidateTime;
}
public Integer getSessionValidationInterval() {
return sessionValidationInterval;
}
public void setSessionValidationInterval(Integer sessionValidationInterval) {
this.sessionValidationInterval = sessionValidationInterval;
}
public String getFilesUrlPrefix() {
return filesUrlPrefix;
}
public void setFilesUrlPrefix(String filesUrlPrefix) {
this.filesUrlPrefix = filesUrlPrefix;
}
public String getFilesPath() {
return filesPath;
}
public void setFilesPath(String filesPath) {
this.filesPath = filesPath;
}
public Integer getHeartbeatTimeout() {
return heartbeatTimeout;
}
public void setHeartbeatTimeout(Integer heartbeatTimeout) {
this.heartbeatTimeout = heartbeatTimeout;
}
public String getPicsPath() {
return picsPath;
}
public void setPicsPath(String picsPath) {
this.picsPath = picsPath;
}
public String getPosapiUrlPrefix() {
return posapiUrlPrefix;
}
public void setPosapiUrlPrefix(String posapiUrlPrefix) {
this.posapiUrlPrefix = posapiUrlPrefix;
}
}

View File

@ -0,0 +1,109 @@
package com.xncoding.pos.controller;
import com.xncoding.pos.config.properties.MyProperties;
import com.xncoding.pos.exception.ForbiddenUserException;
import com.xncoding.pos.service.ManagerInfoService;
import com.xncoding.pos.shiro.IncorrectCaptchaException;
import com.xncoding.pos.shiro.ShiroKit;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;
/**
* Description: 登录验证
*/
// 只用同时具有permission:view和permission:aix权限才能访问
//@RequiresPermissions(value={"permission:view","permission:aix"}, logical= Logical.AND)
//@RequiresPermissions(value={"permission:view","permission:aix"}, logical= Logical.OR)一个就行
@Controller
public class LoginController {
@Resource
private ManagerInfoService managerInfoService;
@Resource
private MyProperties myProperties;
private static final Logger _logger = LoggerFactory.getLogger(LoginController.class);
//登录页(shiro配置需要两个/login 接口,一个是get用来获取登陆页面,一个用post用于登录)
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
if (ShiroKit.isAuthenticated()) {
return "redirect:/";
}
return "login";
}
// 登录提交地址和applicationontext-shiro.xml配置的loginurl一致。 (配置文件方式的说法)
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(HttpServletRequest request, Map<String, Object> map) {
_logger.info("登录方法start.........");
// 登录失败从request中获取shiro处理的异常信息。shiroLoginFailure:就是shiro异常类的全类名.
Object exception = request.getAttribute("shiroLoginFailure");
String msg;
if (exception != null) {
if (UnknownAccountException.class.isInstance(exception)) {
msg = "用户名不正确,请重新输入";
} else if (IncorrectCredentialsException.class.isInstance(exception)) {
msg = "密码错误,请重新输入";
} else if (IncorrectCaptchaException.class.isInstance(exception)) {
msg = "验证码错误";
} else if (ForbiddenUserException.class.isInstance(exception)) {
msg = "该用户已被禁用,如有疑问请联系系统管理员。";
} else {
msg = "发生未知错误,请联系管理员。";
}
map.put("username", request.getParameter("username"));
map.put("password", request.getParameter("password"));
map.put("msg", msg);
return "login";
}
//如果已经登录,直接跳转主页面
return "index";
}
/**
* 主页
* @param session
* @param model
* @return
*/
@RequestMapping({"/", "/index"})
public String index(HttpSession session, Model model) {
// _logger.info("访问首页start...");
// 做一些其他事情比如把项目的数量放到session中
if (ShiroKit.hasRole("admin") && session.getAttribute("projectNum") == null) {
session.setAttribute("projectNum", 2);
}
if (session.getAttribute("picsUrlPrefix") == null) {
// 图片访问URL前缀
session.setAttribute("picsUrlPrefix", myProperties.getPicsUrlPrefix());
}
return "index";
}
/**
* 欢迎页面
* @param request
* @param model
* @return
*/
@RequestMapping("/welcome")
public String welcome(HttpServletRequest request, Model model) {
return "modules/common/welcome";
}
}

View File

@ -0,0 +1,92 @@
package com.xncoding.pos.dao.entity;
import com.xncoding.pos.common.dao.entity.Manager;
import java.io.Serializable;
import java.util.List;
/**
* Description: 后台运维管理员信息
*/
public class ManagerInfo extends Manager implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 状态
*/
private String stateStr;
/**
* 所属项目id列表逗号分隔
*/
private String pids;
/**
* 所属项目名列表(逗号分隔)
*/
private String pnames;
/**
* 所属项目id列表
*/
private List<Integer> pidsList;
/**
* 一个管理员具有多个角色
*/
private List<SysRole> roles;// 一个用户具有多个角色
public ManagerInfo() {
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
/**
* 密码盐
*/
public String getCredentialsSalt() {
return getUsername() + getSalt();
}
@Override
public String toString() {
return "username:" + getUsername() + "|name=" + getName();
}
public String getStateStr() {
return stateStr;
}
public void setStateStr(String stateStr) {
this.stateStr = stateStr;
}
public String getPids() {
return pids;
}
public void setPids(String pids) {
this.pids = pids;
}
public List<Integer> getPidsList() {
return pidsList;
}
public void setPidsList(List<Integer> pidsList) {
this.pidsList = pidsList;
}
public String getPnames() {
return pnames;
}
public void setPnames(String pnames) {
this.pnames = pnames;
}
}

View File

@ -0,0 +1,29 @@
package com.xncoding.pos.dao.entity;
import com.xncoding.pos.common.dao.entity.Permission;
import com.xncoding.pos.common.dao.entity.Role;
import java.io.Serializable;
import java.util.List;
/**
* Description : 角色信息
*/
public class SysRole extends Role implements Serializable {
private static final long serialVersionUID = 1L;
// 拥有的权限列表
private List<Permission> permissions;
public SysRole() {
}
public List<Permission> getPermissions() {
return permissions;
}
public void setPermissions(List<Permission> permissions) {
this.permissions = permissions;
}
}

View File

@ -0,0 +1,15 @@
package com.xncoding.pos.dao.repository;
import com.baomidou.mybatisplus.plugins.pagination.Pagination;
import com.xncoding.pos.common.dao.repository.ManagerMapper;
import com.xncoding.pos.dao.entity.ManagerInfo;
import java.util.List;
import java.util.Map;
/**
* Description :
*/
public interface ManagerInfoDao extends ManagerMapper {
ManagerInfo findByUsername(String username);
}

View File

@ -0,0 +1,46 @@
<?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="com.xncoding.pos.dao.repository.ManagerInfoDao">
<resultMap id="ManagerInfoMap" type="managerInfo">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
<result property="salt" column="salt"/>
<result property="state" column="state"/>
<collection property="roles" ofType="sysRole">
<id property="id" column="role_id"/>
<result property="role" column="role_role"/>
<collection property="permissions" ofType="permission">
<id property="id" column="perm_id"/>
<result property="permission" column="perm_permission"/>
</collection>
</collection>
<collection property="pidsList" ofType="integer">
<result column="project_id"/>
</collection>
</resultMap>
<select id="findByUsername" resultMap="ManagerInfoMap">
SELECT DISTINCT
A.id AS id,
A.username AS username,
A.name AS name,
A.password AS password,
A.salt AS salt,
A.state AS state,
C.id AS role_id,
C.role AS role_role,
E.id AS perm_id,
E.permission AS perm_permission,
F.project_id AS project_id
FROM t_manager A
LEFT JOIN t_manager_role B ON A.id=B.manager_id
LEFT JOIN t_role C ON B.role_id=C.id
LEFT JOIN t_role_permission D ON C.id=D.role_id
LEFT JOIN t_permission E ON D.permission_Id=E.id
LEFT JOIN t_project_user F ON A.id=F.user_id
WHERE username=#{username}
</select>
</mapper>

View File

@ -0,0 +1,14 @@
package com.xncoding.pos.exception;
import org.apache.shiro.authc.AuthenticationException;
/**
* 禁用用户异常
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/12
*/
public class ForbiddenUserException extends AuthenticationException {
}

View File

@ -0,0 +1,73 @@
package com.xncoding.pos.model;
/**
* Controller的基础返回类
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/7
*/
public class BaseResponse<T> {
/**
* 是否成功
*/
private boolean success;
/**
* 说明
*/
private String msg;
/**
* 返回数据
*/
private T data;
/**
* 分页时候的总数
*/
private Integer total;
public BaseResponse() {
}
public BaseResponse(boolean success, String msg, Integer total, T data) {
this.success = success;
this.msg = msg;
this.total = total;
this.data = data;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
}

View File

@ -0,0 +1,42 @@
package com.xncoding.pos.service;
import com.xncoding.pos.dao.entity.ManagerInfo;
import com.xncoding.pos.dao.repository.ManagerInfoDao;
import com.xncoding.pos.exception.ForbiddenUserException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
/**
* 后台用户管理
*/
@Service
public class ManagerInfoService {
@Resource
private ManagerInfoDao managerInfoDao;
/**
* 通过名称查找用户
* @param username
* @return
*/
public ManagerInfo findByUsername(String username) {
ManagerInfo managerInfo = managerInfoDao.findByUsername(username);
if (managerInfo == null) {
throw new UnknownAccountException();
}
if (managerInfo.getState() == 2) {
throw new ForbiddenUserException();
}
if (managerInfo.getPidsList() == null) {
managerInfo.setPidsList(Collections.singletonList(0));
} else if (managerInfo.getPidsList().size() == 0) {
managerInfo.getPidsList().add(0);
}
return managerInfo;
}
}

View File

@ -0,0 +1,29 @@
package com.xncoding.pos.shiro;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* Description : 拓展登陆验证字段
*/
public class CaptchaUsernamePasswordToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1L;
//验证码字符串
private String captcha;
public CaptchaUsernamePasswordToken(String username, char[] password,
boolean rememberMe, String host, String captcha) {
super(username, password, rememberMe, host);
this.captcha = captcha;
}
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
}

View File

@ -0,0 +1,28 @@
package com.xncoding.pos.shiro;
import org.apache.shiro.authc.AuthenticationException;
/**
* Description : 验证码错误异常
*/
public class IncorrectCaptchaException extends AuthenticationException {
private static final long serialVersionUID = 1L;
public IncorrectCaptchaException() {
super();
}
public IncorrectCaptchaException(String message, Throwable cause) {
super(message, cause);
}
public IncorrectCaptchaException(String message) {
super(message);
}
public IncorrectCaptchaException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,107 @@
package com.xncoding.pos.shiro;
import com.xncoding.pos.dao.entity.ManagerInfo;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* Description: 验证码过滤器此过滤器已经在shiro中配置这里不需要再次配置拦截路径
*/
public class KaptchaFilter extends FormAuthenticationFilter {
public static final String DEFAULT_CAPTCHA_PARAM = "captcha";
private String captchaParam = DEFAULT_CAPTCHA_PARAM;
private static final Logger _logger = LoggerFactory.getLogger(KaptchaFilter.class);
//登录验证
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response)
throws Exception {
CaptchaUsernamePasswordToken token = createToken(request, response);
try {
_logger.info("KaptchaFilter.executeLogin");
/*图形验证码验证*/
doCaptchaValidate((HttpServletRequest) request, token);
Subject subject = getSubject(request, response);
subject.login(token);//正常验证
//到这里就算验证成功了,把用户信息放到session中
ManagerInfo user = ShiroKit.getUser();
((HttpServletRequest) request).getSession().setAttribute("user", user);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
ServletRequest request, ServletResponse response) throws Exception {
issueSuccessRedirect(request, response);
//we handled the success redirect directly, prevent the chain from continuing:
return false;
}
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
WebUtils.issueRedirect(request, response, "/", null, true);
}
// 验证码校验
protected void doCaptchaValidate(HttpServletRequest request, CaptchaUsernamePasswordToken token) {
_logger.info("KaptchaFilter.doCaptchaValidate");
//session中的图形码字符串
String captcha = (String) request.getSession().getAttribute(
com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
_logger.info("session中的图形码字符串:" + captcha);
//比对
if (captcha == null || !captcha.equalsIgnoreCase(token.getCaptcha())) {
throw new IncorrectCaptchaException();
}
}
@Override
protected CaptchaUsernamePasswordToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
String captcha = getCaptcha(request);
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return new CaptchaUsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha);
}
public String getCaptchaParam() {
return captchaParam;
}
public void setCaptchaParam(String captchaParam) {
this.captchaParam = captchaParam;
}
protected String getCaptcha(ServletRequest request) {
return WebUtils.getCleanParam(request, getCaptchaParam());
}
//保存异常对象到request
@Override
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
request.setAttribute(getFailureKeyAttribute(), ae);
}
}

View File

@ -0,0 +1,24 @@
package com.xncoding.pos.shiro;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Description : 自定义异常处理类
*/
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//如果是shiro无权操作因为shiro 在操作auno等一部分不进行转发至无权限url
if (ex instanceof UnauthorizedException) {
return new ModelAndView("error/shiro_403");
}
return null;
}
}

View File

@ -0,0 +1,126 @@
package com.xncoding.pos.shiro;
import com.xncoding.pos.common.dao.entity.Permission;
import com.xncoding.pos.service.ManagerInfoService;
import com.xncoding.pos.dao.entity.ManagerInfo;
import com.xncoding.pos.dao.entity.SysRole;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Description : 身份校验核心类
*/
public class MyShiroRealm extends AuthorizingRealm {
private static final Logger _logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
ManagerInfoService managerInfoService;
/**
* 认证信息.(身份验证)
* Authentication 是用来验证用户身份
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
_logger.info("MyShiroRealm.doGetAuthenticationInfo()");
//获取用户的输入的账号.
String username = (String) token.getPrincipal();
//_logger.info("用户的账号:"+username);
//通过username从数据库中查找 ManagerInfo对象
//实际项目中这里可以根据实际情况做缓存如果不做Shiro自己也是有时间间隔机制2分钟内不会重复执行该方法
ManagerInfo managerInfo = managerInfoService.findByUsername(username);
if (managerInfo == null) {
return null;
}
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
managerInfo, //用户
managerInfo.getPassword(), //密码
ByteSource.Util.bytes(managerInfo.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
//明文: 若存在将此用户存放到登录认证info中无需自己做密码对比Shiro会为我们进行密码对比校验
// SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
// managerInfo, //用户名
// managerInfo.getPassword(), //密码
// getName() //realm name
// );
return authenticationInfo;
}
/**
* 此方法调用hasRole,hasPermission的时候才会进行回调.
* <p>
* 权限信息.(授权):
* 1、如果用户正常退出缓存自动清空
* 2、如果用户非正常退出缓存自动清空
* 3、如果我们修改了用户的权限而用户不退出系统修改的权限无法立即生效。
* 需要手动编程进行实现放在service进行调用
* 在权限修改后调用realm中的方法realm已经由spring管理所以从spring中获取realm实例调用clearCached方法
* :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/*
* 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行,
* 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理;
* 当放到缓存中时这样的话doGetAuthorizationInfo就只会执行一次了
* 缓存过期之后会再次执行。
*/
_logger.info("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
ManagerInfo managerInfo = (ManagerInfo) principals.getPrimaryPrincipal();
//设置相应角色的权限信息
for (SysRole role : managerInfo.getRoles()) {
//设置角色
authorizationInfo.addRole(role.getRole());
for (Permission p : role.getPermissions()) {
//设置权限
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
/**
* 设置认证加密方式
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.HASH_ALGORITHM_NAME);
md5CredentialsMatcher.setHashIterations(ShiroKit.HASH_ITERATIONS);
super.setCredentialsMatcher(md5CredentialsMatcher);
}
}

View File

@ -0,0 +1,32 @@
package com.xncoding.pos.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* Description : 角色过滤器,为了实现or的效果就使用这个过滤器,shiro默认是and的效果
*/
public class RoleFilter extends RolesAuthorizationFilter {
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws IOException {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
for (int i = 0; i < rolesArray.length; i++) {
if (subject.hasRole(rolesArray[i])) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,252 @@
/**
* Copyright (c) 2015-2017, Chill Zhuang 庄骞 (smallchill@163.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xncoding.pos.shiro;
import com.xncoding.pos.dao.entity.ManagerInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
/**
* shiro工具类
*
* @author dafei, Chill Zhuang
*/
public class ShiroKit {
private static final String NAMES_DELIMETER = ",";
/**
* 散列算法
*/
public final static String HASH_ALGORITHM_NAME = "MD5";
/**
* 循环次数
*/
public final static int HASH_ITERATIONS = 2;
/**
* shiro密码加密工具类
*
* @param credentials 密码
* @param saltSource 密码盐
* @return
*/
public static String md5(String credentials, String saltSource) {
return new SimpleHash(HASH_ALGORITHM_NAME, credentials, saltSource, HASH_ITERATIONS).toHex();
}
/**
* 获取随机盐值
*
* @param length 字节长度一个字节2位16进制数表示
* @return
*/
public static String getRandomSalt(int length) {
return new SecureRandomNumberGenerator().nextBytes(length).toHex();
}
/**
* 获取当前 Subject
*
* @return Subject
*/
public static Subject getSubject() {
return SecurityUtils.getSubject();
}
/**
* 获取封装的 ShiroUser
*
* @return ShiroUser
*/
public static ManagerInfo getUser() {
if (isGuest()) {
return null;
} else {
return (ManagerInfo) getSubject().getPrincipals().getPrimaryPrincipal();
}
}
/**
* 从shiro获取session
*/
public static Session getSession() {
return getSubject().getSession();
}
/**
* 获取shiro指定的sessionKey
*/
@SuppressWarnings("unchecked")
public static <T> T getSessionAttr(String key) {
Session session = getSession();
return session != null ? (T) session.getAttribute(key) : null;
}
/**
* 设置shiro指定的sessionKey
*/
public static void setSessionAttr(String key, Object value) {
Session session = getSession();
session.setAttribute(key, value);
}
/**
* 移除shiro指定的sessionKey
*/
public static void removeSessionAttr(String key) {
Session session = getSession();
if (session != null)
session.removeAttribute(key);
}
/**
* 验证当前用户是否属于该角色?,使用时与lacksRole 搭配使用
*
* @param roleName 角色名
* @return 属于该角色true否则false
*/
public static boolean hasRole(String roleName) {
return getSubject() != null && roleName != null
&& roleName.length() > 0 && getSubject().hasRole(roleName);
}
/**
* 与hasRole标签逻辑相反当用户不属于该角色时验证通过。
*
* @param roleName 角色名
* @return 不属于该角色true否则false
*/
public static boolean lacksRole(String roleName) {
return !hasRole(roleName);
}
/**
* 验证当前用户是否属于以下任意一个角色。
*
* @param roleNames 角色列表
* @return 属于:true,否则false
*/
public static boolean hasAnyRoles(String roleNames) {
boolean hasAnyRole = false;
Subject subject = getSubject();
if (subject != null && roleNames != null && roleNames.length() > 0) {
for (String role : roleNames.split(NAMES_DELIMETER)) {
if (subject.hasRole(role.trim())) {
hasAnyRole = true;
break;
}
}
}
return hasAnyRole;
}
/**
* 验证当前用户是否属于以下所有角色。
*
* @param roleNames 角色列表
* @return 属于:true,否则false
*/
public static boolean hasAllRoles(String roleNames) {
boolean hasAllRole = true;
Subject subject = getSubject();
if (subject != null && roleNames != null && roleNames.length() > 0) {
for (String role : roleNames.split(NAMES_DELIMETER)) {
if (!subject.hasRole(role.trim())) {
hasAllRole = false;
break;
}
}
}
return hasAllRole;
}
/**
* 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用
*
* @param permission 权限名
* @return 拥有权限true否则false
*/
public static boolean hasPermission(String permission) {
return getSubject() != null && permission != null
&& permission.length() > 0
&& getSubject().isPermitted(permission);
}
/**
* 与hasPermission标签逻辑相反当前用户没有制定权限时验证通过。
*
* @param permission 权限名
* @return 拥有权限true否则false
*/
public static boolean lacksPermission(String permission) {
return !hasPermission(permission);
}
/**
* 已认证通过的用户不包含已记住的用户这是与user标签的区别所在。与notAuthenticated搭配使用
*
* @return 通过身份验证true否则false
*/
public static boolean isAuthenticated() {
return getSubject() != null && getSubject().isAuthenticated();
}
/**
* 未认证通过用户与authenticated标签相对应。与guest标签的区别是该标签包含已记住用户。。
*
* @return 没有通过身份验证true否则false
*/
public static boolean notAuthenticated() {
return !isAuthenticated();
}
/**
* 认证通过或已记住的用户。与guset搭配使用。
*
* @return 用户true否则 false
*/
public static boolean isUser() {
return getSubject() != null && getSubject().getPrincipal() != null;
}
/**
* 验证当前用户是否为“访客”即未认证包含未记住的用户。用user搭配使用
*
* @return 访客true否则false
*/
public static boolean isGuest() {
return !isUser();
}
/**
* 输出当前用户信息,通常为登录帐号信息。
*
* @return 当前用户信息
*/
public static String principal() {
if (getSubject() != null) {
Object principal = getSubject().getPrincipal();
return principal.toString();
}
return "";
}
}

View File

@ -0,0 +1,116 @@
##########################################################
################## 所有profile共有的配置 #################
##########################################################
################### 自定义项目配置 ###################
xncoding:
kaptcha-open: true #是否开启登录时验证码 (true/false)
session-open: false #是否开启session验证 (true/false)
session-invalidate-time: 7200 #session失效时间 单位:秒
session-validation-interval: 3600 #多久检测一次失效的session 单位:秒
heartbeat-timeout: 10 # 机具心跳报告超时时间 单位:分钟
################### 项目启动端口 ###################
server.port: 8092
################### spring配置 ###################
spring:
profiles:
active: dev
thymeleaf:
mode: HTML
cache: false
mvc:
view:
prefix: /templates
http:
multipart:
max-request-size: 100MB #最大请求大小
max-file-size: 100MB #最大文件大小
################### mybatis-plus配置 ###################
mybatis-plus:
mapper-locations: classpath*:com/xncoding/pos/dao/repository/mapping/*.xml
typeAliasesPackage: >
com.xncoding.pos.api.model,
com.xncoding.pos.dao.entity,
com.xncoding.pos.common.dao.entity
global-config:
id-type: 0 # 0:数据库ID自增 1:用户输入id 2:全局唯一id(IdWorker) 3:全局唯一ID(uuid)
db-column-underline: false
refresh-mapper: true
configuration:
map-underscore-to-camel-case: true
cache-enabled: true #配置的缓存的全局开关
lazyLoadingEnabled: true #延时加载的开关
multipleResultSetsEnabled: true #开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性
################### spring security配置 ###################
security:
ignored: /static/**
logging:
level:
org.springframework.web.servlet: ERROR
---
#####################################################################
######################## 开发环境profile ##########################
#####################################################################
spring:
profiles: dev
datasource:
url: jdbc:mysql://123.207.66.156:3306/pos?useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8
username: root
password: _EnZhi123
thymeleaf:
cache: false
################### 自定义项目配置 ###################
xncoding:
excel-path: E:/home/
files-path: E:/home/
files-url-prefix: https://show.xncoding.net/files/ # 文件访问URL前缀
pics-path: E:/home/
pics-url-prefix: https://show.xncoding.net/pics/ # 图片访问URL前缀
posapi-url-prefix: http://123.207.66.156:9095
logging:
level:
ROOT: INFO
com:
xncoding: DEBUG
file: E:/logs/app-manage.log
---
#####################################################################
######################## 测试环境profile ##########################
#####################################################################
spring:
profiles: test
datasource:
url: jdbc:mysql://123.207.66.156:3306/pos?useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8
username: root
password: _EnZhi123
thymeleaf:
cache: false
################### 自定义项目配置 ###################
xncoding:
excel-path: /var/data/
files-path: /usr/share/nginx/html/files/
files-url-prefix: https://show.xncoding.net/files/ # 文件访问URL前缀
pics-path: /usr/share/nginx/html/pics/
pics-url-prefix: https://show.xncoding.net/pics/ # 图片访问URL前缀
# posapi-url-prefix: http://posapi.enzhico.net
posapi-url-prefix: http://123.207.66.156:9095
logging:
level:
ROOT: INFO
com:
xncoding: DEBUG
file: /var/logs/app-manage.log

View File

@ -0,0 +1,23 @@
_____ _______ _____ _____
/\ \ /::\ \ /\ \ /\ \
/::\____\ /::::\ \ /::\____\ /::\ \
/:::/ / /::::::\ \ /:::/ / /::::\ \
/:::/ / /::::::::\ \ /:::/ / /::::::\ \
/:::/ / /:::/~~\:::\ \ /:::/ / /:::/\:::\ \
/:::/ / /:::/ \:::\ \ /:::/____/ /:::/__\:::\ \
/:::/ / /:::/ / \:::\ \ |::| | /::::\ \:::\ \
/:::/ / /:::/____/ \:::\____\ |::| | _____ /::::::\ \:::\ \
/:::/ / |:::| | |:::| | |::| | /\ \ /:::/\:::\ \:::\ \
/:::/____/ |:::|____| |:::| | |::| | /::\____\/:::/__\:::\ \:::\____\
\:::\ \ \:::\ \ /:::/ / |::| | /:::/ /\:::\ \:::\ \::/ /
\:::\ \ \:::\ \ /:::/ / |::| | /:::/ / \:::\ \:::\ \/____/
\:::\ \ \:::\ /:::/ / |::|____|/:::/ / \:::\ \:::\ \
\:::\ \ \:::\__/:::/ / |:::::::::::/ / \:::\ \:::\____\
\:::\ \ \::::::::/ / \::::::::::/____/ \:::\ \::/ /
\:::\ \ \::::::/ / ~~~~~~~~~~ \:::\ \/____/
\:::\ \ \::::/ / \:::\ \
\:::\____\ \::/____/ \:::\____\
\::/ / ~~ \::/ /
\/____/ \/____/

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false" monitoring="autodetect"
dynamicConfig="true">
<diskStore path="java.io.tmpdir/ehcache"/>
<defaultCache
maxElementsInMemory="50000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="true"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!-- 全局变量:永不过期-->
<cache name="CONSTANT"
maxElementsInMemory="50000"
eternal="true"
clearOnFlush="false"
overflowToDisk="true"
diskSpoolBufferSizeMB="1024"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
</cache>
<cache name="TOKEN_CACHE"
maxElementsInMemory="50000"
eternal="true"
clearOnFlush="false"
overflowToDisk="true"
diskSpoolBufferSizeMB="1024"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
</cache>
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
overflowToDisk="true"
eternal="true"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
<cache name="org.apache.shiro.realm.text.PropertiesRealm-0-accounts"
maxElementsInMemory="1000"
eternal="true"
overflowToDisk="true"/>
</ehcache>
<!--
maxElementsInMemory="10000" //Cache中最多允许保存的数据对象的数量
external="false" //缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期
timeToLiveSeconds="3600" //缓存的存活时间,从开始创建的时间算起
timeToIdleSeconds="3600" //多长时间不访问该缓存那么ehcache 就会清除该缓存
这两个参数很容易误解看文档根本没用我仔细分析了ehcache的代码。结论如下
1、timeToLiveSeconds的定义是以创建时间为基准开始计算的超时时长
2、timeToIdleSeconds的定义是在创建时间和最近访问时间中取出离现在最近的时间作为基准计算的超时时长
3、如果仅设置了timeToLiveSeconds则该对象的超时时间=创建时间+timeToLiveSeconds假设为A
4、如果没设置timeToLiveSeconds则该对象的超时时间=min(创建时间,最近访问时间)+timeToIdleSeconds假设为B
5、如果两者都设置了则取出A、B最少的值即min(A,B),表示只要有一个超时成立即算超时。
overflowToDisk="true" //内存不足时,是否启用磁盘缓存
diskSpoolBufferSizeMB //设置DiskStore磁盘缓存的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
maxElementsOnDisk //硬盘最大缓存个数
diskPersistent //是否缓存虚拟机重启期数据The default value is false
diskExpiryThreadIntervalSeconds //磁盘失效线程运行时间间隔默认是120秒。
memoryStoreEvictionPolicy="LRU" //当达到maxElementsInMemory限制时Ehcache将会根据指定的策略去清理内存。默认策略是LRU最近最少使用。你可以设置为FIFO先进先出或是LFU较少使用
clearOnFlush //内存数量最大时是否清除
maxEntriesLocalHeap="0"
maxEntriesLocalDisk="1000"
-->

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,36 @@
body, div {
margin: 0;
padding: 0;
}
body {
background: url("../img/error/error_bg.jpg") repeat-x scroll 0 0 #67ACE4;
}
#container {
margin: 0 auto;
padding-top: 50px;
text-align: center;
width: 560px;
}
#container img {
border: medium none;
margin-bottom: 50px;
}
#container .error {
height: 200px;
position: relative;
}
#container .error img {
bottom: -50px;
position: absolute;
right: -50px;
}
#container .msg {
margin-bottom: 65px;
}
#cloud {
background: url("../img/error/error_cloud.png") repeat-x scroll 0 0 transparent;
bottom: 0;
height: 170px;
position: absolute;
width: 100%;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,84 @@
html{height: 100%;}
body.signin {
background: #18c8f6;
height: auto;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
color: rgba(255,255,255,.95);
}
.signinpanel {
width: 750px;
margin: 10% auto 0 auto;
}
.signinpanel .logopanel {
float: none;
width: auto;
padding: 0;
background: none;
}
.signinpanel .signin-info ul {
list-style: none;
padding: 0;
margin: 20px 0;
}
.signinpanel .form-control {
display: block;
margin-top: 15px;
}
.signinpanel .btn {
margin-top: 15px;
}
.signinpanel form {
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255,255,255,.3);
-moz-box-shadow: 0 3px 0 rgba(12, 12, 12, 0.03);
-webkit-box-shadow: 0 3px 0 rgba(12, 12, 12, 0.03);
box-shadow: 0 3px 0 rgba(12, 12, 12, 0.03);
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
padding: 30px;
}
.signup-footer{border-top: solid 1px rgba(255,255,255,.3);margin:20px 0;padding-top: 15px;}
@media screen and (max-width: 768px) {
.signinpanel,
.signuppanel {
margin: 0 auto;
width: 420px!important;
padding: 20px;
}
.signinpanel form {
margin-top: 20px;
}
.signup-footer {
margin-bottom: 10px;
}
.signuppanel .form-control {
margin-bottom: 10px;
}
.signup-footer .pull-left,
.signup-footer .pull-right {
float: none !important;
text-align: center;
}
.signinpanel .signin-info ul {
display: none;
}
}
@media screen and (max-width: 320px) {
.signinpanel,
.signuppanel {
margin:0 20px;
width:auto;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,73 @@
# -------------------------------------以下业务表开始-------------------------------------------
# CREATE DATABASE IF NOT EXISTS pos default charset utf8 COLLATE utf8_general_ci;
# SET FOREIGN_KEY_CHECKS=0;
# USE pos;
# -------------------------------------以下用户管理表开始-------------------------------------------
-- 后台管理用户表
DROP TABLE IF EXISTS `t_manager`;
CREATE TABLE `t_manager` (
`id` INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`username` VARCHAR(32) NOT NULL COMMENT '账号',
`name` VARCHAR(16) DEFAULT '' COMMENT '名字',
`password` VARCHAR(128) DEFAULT '' COMMENT '密码',
`salt` VARCHAR(64) DEFAULT '' COMMENT 'md5密码盐',
`phone` VARCHAR(32) DEFAULT '' COMMENT '联系电话',
`tips` VARCHAR(255) COMMENT '备注',
`state` TINYINT(1) DEFAULT 1 COMMENT '状态 1:正常 2:禁用',
`created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='后台管理用户表';
INSERT INTO `t_manager` VALUES (1,'admin','系统管理员','4a496ba2a4172c71540fa643ddc8bb7c','b4752b4b73034de06afb2db30fe19061', '17890908889', '系统管理员', 1, '2017-12-12 09:46:12', '2017-12-12 09:46:12');
INSERT INTO `t_manager` VALUES (2,'aix','张三','2412d3972722eb186f69a8f4011fbd48','20545a7eaea0241ddf6652a3f9a4ae24', '17859569358', '', 1, '2017-12-12 09:46:12', '2017-12-12 09:46:12');
-- 角色表
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`role` VARCHAR(16) DEFAULT '' COMMENT '角色名称',
`description` VARCHAR(255) DEFAULT '' COMMENT '角色说明',
`created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='角色表';
INSERT INTO `t_role` VALUES (1,'admin','超级管理员', '2017-12-12 09:46:12', '2017-12-12 09:46:12');
INSERT INTO `t_role` VALUES (2,'aix','系统监控员', '2017-12-12 09:46:12', '2017-12-12 09:46:12');
-- 用户角色关联表
DROP TABLE IF EXISTS `t_manager_role`;
CREATE TABLE `t_manager_role` (
`id` INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`manager_id` INT(11) NOT NULL COMMENT '管理用户ID',
`role_id` INT(11) NOT NULL COMMENT '角色ID',
`created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户角色关联表';
INSERT INTO `t_manager_role` VALUES (1, 1, 1, '2017-05-05 00:00:00','2017-05-05 00:00:00');
INSERT INTO `t_manager_role` VALUES (2, 2, 2, '2017-05-05 00:00:00','2017-05-05 00:00:00');
-- 权限表
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission` (
`id` INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`permission` VARCHAR(16) DEFAULT '' COMMENT '权限名称',
`description` VARCHAR(255) DEFAULT '' COMMENT '权限说明',
`created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='权限表';
INSERT INTO `t_permission` VALUES (1,'permission:admin','超级管理权限', '2017-12-12 09:46:12', '2017-12-12 09:46:12');
INSERT INTO `t_permission` VALUES (2,'permission:aix','监控权限', '2017-12-12 09:46:12', '2017-12-12 09:46:12');
INSERT INTO `t_permission` VALUES (3,'permission:adduser','添加用户权限', '2017-12-12 09:46:12', '2017-12-12 09:46:12');
-- 角色权限关联表
DROP TABLE IF EXISTS `t_role_permission`;
CREATE TABLE `t_role_permission` (
`id` INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`role_id` INT(11) NOT NULL COMMENT '角色ID',
`permission_id` INT(11) NOT NULL COMMENT '权限ID',
`created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='角色权限关联表';
INSERT INTO `t_role_permission` VALUES (1, 1, 1, '2017-12-12 09:46:12', '2017-12-12 09:46:12');
INSERT INTO `t_role_permission` VALUES (2, 1, 3, '2017-12-12 09:46:12', '2017-12-12 09:46:12');
INSERT INTO `t_role_permission` VALUES (3, 2, 2, '2017-12-12 09:46:12', '2017-12-12 09:46:12');

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>404-页面未找到</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="shortcut icon" th:href="@{/favicon.ico}"/>
<link th:href="@{/static/css/bootstrap.min.css}" rel="stylesheet"/>
<link rel="stylesheet" type="text/css" th:href="@{/static/css/404.css}" media="screen"/>
</head>
<body>
<div id="container">
<img class="png" th:src="@{/static/img/error/404.png}" alt=""/>
<img class="png msg" th:src="@{/static/img/error/404_msg.png}" alt=""/>
<p>
<a href="/"><img class="png" th:src="@{/static/img/error/404_to_index.png}" alt=""/></a>
</p>
</div>
<div id="cloud" class="png"></div>
<pre style="DISPLAY: none"></pre>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>500 错误页面</title>
</head>
<body>
<h1>500 Error</h1>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>机具管理平台</title>
</head>
<body>
<h1>没有权限</h1>
</body>
</html>

View File

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit">
<title>SpringBoot Shiro测试</title>
<link rel="shortcut icon" th:href="@{/favicon.ico}"/>
<link th:href="@{/static/css/bootstrap.min.css}" rel="stylesheet"/>
<link th:href="@{/static/css/style.css}" rel="stylesheet"/>
</head>
<body class="fixed-sidebar full-height-layout gray-bg" style="overflow:hidden">
<div id="wrapper">
<!--左侧导航开始-->
<nav class="navbar-default navbar-static-side" role="navigation">
<div class="nav-close"><i class="fa fa-times-circle"></i>
</div>
<div class="sidebar-collapse">
<ul class="nav" id="side-menu">
<li class="nav-header">
<div class="dropdown profile-element">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">
<span class="clear">
<span class="block m-t-xs"><strong class="font-bold" th:text="${session.user.username}">Beaut-zihan</strong></span>
<span class="text-muted text-xs block">[[${session.user.name}]]<b class="caret"></b></span>
</span>
</a>
<ul class="dropdown-menu animated fadeInRight m-t-xs">
<li><a th:href="@{/password}">修改密码</a></li>
<li><a href="javascript:void(0);" onclick="logout()">安全退出</a></li>
</ul>
</div>
<div class="logo-element"></div>
</li>
<li shiro:hasRole="admin">
<a id="plink" class="J_menuItem" th:href="@{/project/index.html}"><span class="nav-label">项目管理</span></a>
</li>
<li shiro:hasRole="admin" th:if="${session.projectNum} and (${session.projectNum} gt 0)">
<a class="J_menuItem" th:href="@{/user/index.html}"><span class="nav-label">用户管理</span></a>
</li>
<li shiro:lacksRole="admin">`
<a class="J_menuItem" th:href="@{/device/index.html}"><span class="nav-label">机具入网管理</span></a>
</li>
<li shiro:lacksRole="admin">
<a class="J_menuItem" th:href="@{/monitor/index.html}"><span class="nav-label">机具状态监控</span></a>
</li>
<li shiro:lacksRole="admin">
<a class="J_menuItem" th:href="@{/app/index.html}" data-index="0">App管理</a>
</li>
</ul>
</div>
</nav>
<!--左侧导航结束-->
<!--右侧部分开始-->
<div id="page-wrapper" class="gray-bg dashbard-1">
<div class="row content-tabs">
<button class="roll-nav roll-left J_tabLeft"><i class="fa fa-backward"></i>
</button>
<nav class="page-tabs J_menuTabs">
<div class="page-tabs-content">
<a href="javascript:void(0);" class="active J_menuTab" data-id="main.html">首页</a>
</div>
</nav>
<button class="roll-nav roll-right J_tabRight"><i class="fa fa-forward"></i>
</button>
<div class="btn-group roll-nav roll-right">
<button class="dropdown J_tabClose" data-toggle="dropdown">关闭操作<span class="caret"></span>
</button>
<ul role="menu" class="dropdown-menu dropdown-menu-right">
<li class="J_tabShowActive"><a>定位当前选项卡</a>
</li>
<li class="divider"></li>
<li class="J_tabCloseAll"><a>关闭全部选项卡</a>
</li>
<li class="J_tabCloseOther"><a>关闭其他选项卡</a>
</li>
</ul>
</div>
<a href="javascript:void(0);" onclick="logout()" class="roll-nav roll-right J_tabExit"><i class="fa fa fa-sign-out"></i> 退出</a>
</div>
<div class="row J_mainContent" id="content-main">
<iframe class="J_iframe" name="iframe0" width="100%" height="100%" th:src="@{/welcome}"
frameborder="0" data-id="@{/welcome}" seamless></iframe>
</div>
</div>
<!--右侧部分结束-->
</div>
<script th:src="@{/static/js/jquery.min.js}"></script>
<script th:src="@{/static/js/bootstrap.min.js}"></script>
<script>
function logout() {
window.location = '/logout';
}
</script>
</body>
</html>

View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>SpringBoot Shiro测试</title>
<link rel="shortcut icon" href="../public/favicon.ico" th:href="@{/favicon.ico}"/>
<link href="../public/static/css/bootstrap.min.css" th:href="@{/static/css/bootstrap.min.css}" rel="stylesheet"/>
<link href="../public/static/css/style.css" th:href="@{/static/css/style.css}" rel="stylesheet"/>
<script>if(window.top !== window.self){ window.top.location = window.location;}</script>
</head>
<body class="gray-bg">
<div class="middle-box text-center loginscreen animated fadeInDown">
<div>
<div>
<h1 class="logo-name" style="height: 170px;"></h1>
</div>
<h2>欢迎使用XX管理平台</h2>
<div th:if="${msg}">
<div id="login-alert1" class="alert alert-danger col-sm-12" th:text="${msg}"></div>
</div>
<div th:else>
<div id="login-alert2" class="alert alert-danger col-sm-12" style="display: none;"></div>
</div>
<form class="m-t" id="form" role="form" method="post" th:action="@{/login}">
<div class="form-group">
<input id="login-username" th:value="${username}" name="username" type="text" autocomplete="off"
class="form-control" placeholder="用户名" oninvalid="this.setCustomValidity('请输入用户名')" oninput="setCustomValidity('')">
</div>
<div class="form-group">
<input id="login-password" th:value="${password}" name="password" type="password" autocomplete="off"
class="form-control" placeholder="密码" oninvalid="this.setCustomValidity('请输入密码')" oninput="setCustomValidity('')">
</div>
<div class="form-group" style="padding-bottom: 33px;">
<div class="col-sm-8" style="padding-left: 0; padding-right: 0;">
<input class="form-control" type="text" name="captcha" placeholder="验证码" autocomplete="off"
oninvalid="this.setCustomValidity('请输入验证码')" oninput="setCustomValidity('')">
</div>
<div class="col-sm-4" style="padding-left: 20px;">
<img th:src="@{/kaptcha.jpg}" id="kaptcha" width="100%" height="100%"/>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary block full-width m-b">登 录</button>
</div>
<!--<p class="text-muted text-center">-->
<!--<a href="login.html#"><small>忘记密码了?</small></a> | <a href="#">注册一个新账号</a>-->
<!--</p>-->
</form>
</div>
</div>
<script src="../public/static/js/jquery.min.js?v=2.1.4" th:src="@{/static/js/jquery.min.js}"></script>
<script src="../public/static/js/bootstrap.min.js?v=3.3.6" th:src="@{/static/js/bootstrap.min.js}"></script>
<script>
$(function () {
$("#kaptcha").on('click', function () {
changeKaptcha();
});
$('#form').submit(function() {
var username = $('[name="username"]').val();
var password = $('[name="password"]').val();
if(username == '') {
$('#login-alert').text('请输入用户名').show();
return false;
} else if(password == '') {
$('#login-alert').text('请输入密码').show();
return false;
}
});
});
function changeKaptcha() {
$("#kaptcha").attr('src', '/kaptcha.jpg?' + Math.floor(Math.random() * 100)).fadeIn();
}
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" th:href="@{/favicon.ico}"/>
<link th:href="@{/static/css/bootstrap.min.css}" rel="stylesheet"/>
<link th:href="@{/static/css/style.css}" rel="stylesheet"/>
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content animated fadeInRight">
<div class="ibox-content">
<h3>欢迎您!</h3>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,51 @@
package com.xncoding.pos;
import com.xncoding.pos.shiro.ShiroKit;
import org.junit.Test;
import java.nio.file.*;
/**
* SimpleTest
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/4
*/
public class SimpleTest {
@Test
public void testMd5() {
//盐(用户名+随机数)
String username = "admin";
String salt = ShiroKit.getRandomSalt(16);
//原密码
String password = "12345678";
String encodedPassword = ShiroKit.md5(password, username + salt);
System.out.println("这个是保存进数据库的密码:" + encodedPassword);
System.out.println("这个是保存进数据库的盐:" + salt);
}
@Test
public void test1() throws Exception {
WatchService watchService
= FileSystems.getDefault().newWatchService();
Path path = Paths.get(System.getProperty("user.home"));
path.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println(
"Event kind:" + event.kind()
+ ". File affected: " + event.context() + ".");
}
key.reset();
}
}
}