# erp **Repository Path**: pojnbft/erp ## Basic Information - **Project Name**: erp - **Description**: 前后端分离仓储管理项目 ,后台代码 基于springboot+layui 使用了shiro,redis,fastdfs等技术 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 0 - **Created**: 2020-04-01 - **Last Updated**: 2021-06-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 1.shiro登录session共享问题 修改pom.xml引入shiro-redis org.crazycake shiro-redis 3.2.3 修改application.yml(对所有路径放行,以便进行测试) #shiro的配置 shiro: hash-algorithm-name: md5 hash-iterations: 2 # login-url: /index.html # unauthorized-url: /unauthorized.html anon-urls: - /** - /index.html* - /login.html* - /login/toLogin* - /login/login* logout-url: /login/logout* authc-urls: #- /** 1.属性配置类 ShiroProperties @ConfigurationProperties(prefix = "shiro") @Data public class ShiroProperties { private String hashAlgorithmName = "md5"; private Integer hashIterations = 2; private String loginUrl; private String unauthorizedUrl; private String[] anonUrls; private String logoutUrl; private String[] authcUrls; } 2.shiro配置类 ShiroAutoConfiguration @Configuration @EnableConfigurationProperties(value = {ShiroProperties.class}) public class ShiroAutoConfiguration { @Autowired private ShiroProperties shiroProperties; //redis的配置属性类 @Autowired private RedisProperties redisProperties; /** * 凭证匹配器 */ @Bean public HashedCredentialsMatcher credentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName(shiroProperties.getHashAlgorithmName()); credentialsMatcher.setHashIterations(shiroProperties.getHashIterations()); return credentialsMatcher; } /** * 创建realm */ @Bean public UserRealm userRealm(CredentialsMatcher credentialsMatcher) { UserRealm userRealm = new UserRealm(); userRealm.setCredentialsMatcher(credentialsMatcher); return userRealm; } /** * 声明安全管理器 */ @Bean("securityManager") public SecurityManager securityManager(DefaultWebSessionManager defaultWebSessionManager, SessionDAO redisSession, UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); defaultWebSessionManager.setSessionDAO(redisSession); securityManager.setSessionManager(defaultWebSessionManager); return securityManager; } /** * 配置过滤器 Shiro 的Web过滤器 id必须和web.xml里面的shiroFilter的 targetBeanName的值一样 */ @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); //注入安全管理器 bean.setSecurityManager(securityManager); //注入登陆页面 bean.setLoginUrl(shiroProperties.getLoginUrl()); //注入未授权的页面地址 bean.setUnauthorizedUrl(shiroProperties.getUnauthorizedUrl()); //注入过滤器 Map filterChainDefinition = new HashMap<>(); //注入放行地址 if (shiroProperties.getAnonUrls() != null && shiroProperties.getAnonUrls().length > 0) { String[] anonUrls = shiroProperties.getAnonUrls(); for (String anonUrl : anonUrls) { filterChainDefinition.put(anonUrl, "anon"); } } //注入登出的地址 if (shiroProperties.getLogoutUrl() != null) { filterChainDefinition.put(shiroProperties.getLogoutUrl(), "logout"); } //注拦截的地址 String[] authcUrls = shiroProperties.getAuthcUrls(); if (authcUrls != null && authcUrls.length > 0) { for (String authcUrl : authcUrls) { filterChainDefinition.put(authcUrl, "authc"); } } bean.setFilterChainDefinitionMap(filterChainDefinition); return bean; } /** * 注册过滤器 */ @Bean public FilterRegistrationBean filterRegistrationBeanDelegatingFilterProxy() { FilterRegistrationBean bean = new FilterRegistrationBean<>(); //创建过滤器 DelegatingFilterProxy proxy = new DelegatingFilterProxy(); bean.setFilter(proxy); bean.addInitParameter("targetFilterLifecycle", "true"); bean.addInitParameter("targetBeanName", "shiroFilter"); // bean.addUrlPatterns(); List servletNames = new ArrayList<>(); servletNames.add(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); bean.setServletNames(servletNames); return bean; } /*加入注解的使用,不加入这个注解不生效--开始*/ /** * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /*加入注解的使用,不加入这个注解不生效--结束*/ /** * 使用Redis 来存储登录的信息 * sessionDao 还需要设置给sessionManager */ @Bean public SessionDAO redisSessionDAO(IRedisManager redisManager) { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager); //操作哪个redis redisSessionDAO.setExpire(7 * 24 * 3600); // 用户的登录信息保存多久? 7 天 // redisSessionDAO.setKeySerializer(keySerializer); jdk // redisSessionDAO.setValueSerializer(valueSerializer);jdk return redisSessionDAO; } @Bean public IRedisManager redisManager() { RedisManager redisManager = new RedisManager(); JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(redisProperties.getJedis().getPool().getMaxActive()); // 链接池的最量 20 ,并发特别大时,连接池的数据可以最大增加20个 jedisPoolConfig.setMaxIdle(redisProperties.getJedis().getPool().getMaxIdle());// 连接池的最大剩余量15个 :并发不大,池里面的对象用不上,里面对象太多了。浪费空间 jedisPoolConfig.setMinIdle(redisProperties.getJedis().getPool().getMinIdle()); // 连接池初始就有10 个 JedisPool jedisPool = new JedisPool(jedisPoolConfig, redisProperties.getHost(), redisProperties.getPort(), 2000, redisProperties.getPassword()); redisManager.setJedisPool(jedisPool); return redisManager; } } 3.userRealm 还是原来的,不需要修改 public class UserRealm extends AuthenticatingRealm { @Autowired private UserService userservice; public String getName() { return this.getClass().getName(); } /** * @param authenticationToken 存储信息的token * @return 认证信息 * @throws AuthenticationException 登陆失败会抛出异常 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String userName = (String) authenticationToken.getPrincipal(); //根据用户名查询用户 User user = userservice.queryUserByLoginName(userName); if (null != user) { //创建Activeuser ActiverUser activierUser = new ActiverUser(); activierUser.setUser(user); //创建返回值 ByteSource salt = ByteSource.Util.bytes(user.getSalt()); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(activierUser, user.getPwd(), salt, this.getName()); return info; } else { return null; } } } 4.seesion的管理器 TokenWebSessionManager @Configuration public class TokenWebSessionManager extends DefaultWebSessionManager { private static final String TOKEN_HEADER = "TOKEN"; @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { //从头里面得到请求TOKEN 如果不存在就生成一个 String header = WebUtils.toHttp(request).getHeader(TOKEN_HEADER); if (StringUtils.hasText(header)) { return header; } return UUID.randomUUID().toString(); } } 6.使用 LoginController @Controller @RequestMapping("login") @CrossOrigin public class LoginController { @RequestMapping("doLogin") @ResponseBody public ResultObj doLogin(String loginname, String password) { try { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken loginToken = new UsernamePasswordToken(loginname, password); subject.login(loginToken); //得到shiro的sessionid==token String token = subject.getSession().getId().toString(); return new ResultObj(200, "登陆成功", token); } catch (AuthenticationException e) { e.printStackTrace(); return new ResultObj(-1, "登陆失败,用户名或者密码不正确"); } } } 2.解决前端请求跨域问题 1.前端修改 创建common.js var api = 'http://127.0.0.1:8080/' //下次再发ajax请求把token带到后台 var token = $.cookie('TOKEN'); //如果访问登陆页面这外的页面并且还没有登陆成功之后写入cookie的token就转到登陆页面 if (token == undefined & window.location != 'http://localhost:63342/ERP-WEB/login.html') { window.top.location = '/ERP-WEB/login.html'; } //设置全局ajax拦截,发送请求时携带token $.ajaxSetup({ headers: { 'TOKEN': token } }) 2.登陆页面修改 登录按钮 3.主页修改 引入js 4.创建CorsAutoConfig @Configuration public class CorsAutoConfig { @Bean public CorsFilter corsFilter(){ UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource=new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration=new CorsConfiguration(); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.addAllowedOrigin("*"); urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration); CorsFilter corsFilter=new CorsFilter(urlBasedCorsConfigurationSource); return corsFilter; } } 3.动态验证用户登录 如果cookie里token还在,redis中的数据失效了,用户会处于未登陆的状态,此使需要重定向到登陆页面 如果cookie中的token失效了,需要重定向到登陆页面 1.后端提供接口 修改LoginController @RequestMapping("checkLogin") @ResponseBody public ResultObj checkLogin() { Subject subject = SecurityUtils.getSubject(); boolean authenticated = subject.isAuthenticated(); if (authenticated) { return ResultObj.IS_LOGIN; } else { return ResultObj.UN_LOGIN; } } 2.修改common.js var api = 'http://127.0.0.1:8080/' //下次再发ajax请求把token带到后台 var token = $.cookie('TOKEN'); //设置全局ajax拦截,发送请求时携带token $.ajaxSetup({ headers: { 'TOKEN': token } }) //如果访问登陆页面这外的页面并且还没有登陆成功之后写入cookie的token就转到登陆页面 if (window.location != 'http://localhost:63342/ERP-WEB/login.html') { if (token == undefined) { window.top.location = '/ERP-WEB/login.html'; } else { $.ajax({ url: api + "login/checkLogin", async: true, type: 'post', dataType: 'json', success: function (res) { if (res.code == -1) { window.top.location = '/ERP-WEB/login.html'; } }, error: function (res) { window.top.location = '/ERP-WEB/login.html'; } }); } } 4.转换成json时剔除为空的字段 在属性上加上以下注解: @JsonInclude(JsonInclude.Include.NON_EMPTY) 5.mybatisPlus ,domain中添加数据库中没有的字段 在属性上添加下面的注解 @TableField(exist=false) 6.生成json串时不序列化 在属性上添加下面的注解 @JsonIgnore 7.redis做缓存时,注入service与ioc容器生成代理对象顺序问题解决 在依赖注入过后,通过ioc容器获得相应的service对象 工具类 @Component public class AppUtils implements ApplicationContextAware { private static ApplicationContext context; public static ApplicationContext getContext() { return context; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } } 使用时: @Override public DataGridView queryAllUser(UserVo userVo) { IPage page = new Page<>(userVo.getPage(), userVo.getLimit()); QueryWrapper qw = new QueryWrapper<>(); qw.eq(null != userVo.getAvailable(), "available", userVo.getAvailable()); qw.like(!StringUtils.isBlank(userVo.getDeptid()), "deptid", userVo.getDeptid()); qw.like(!StringUtils.isBlank(userVo.getName()), "name", userVo.getName()); qw.like(!StringUtils.isBlank(userVo.getRemark()), "remark", userVo.getRemark()); qw.eq("type", Constant.USER_TYPE_NORMAL); userMapper.selectPage(page, qw); List users = page.getRecords(); //从ioc容器中获取DeptService实例,从而给用户的部门名称字段赋值 ApplicationContext context = AppUtils.getContext(); DeptService deptService = context.getBean(DeptService.class); for (User user : users) { Dept dept = deptService.getById(user.getDeptid()); user.setDeptname(dept.getTitle()); } return new DataGridView(page.getTotal(), users); } 这样就能保证我们使用的DeptService接口的实例对象,一定是实现redis缓存的代理对象 8.缓存问题 - 问题描述:当使用表格里面是否可用对数据进行更新之后,缓存里面的数据丢失部分 ,原因是因为@CachePut里缓存的是返回的值的对象 - 解决思路:先进行修改,再进行一次查询,将查询到的数据进行返回 9.docker安装redis - 拉取镜像最新版本 docker pull redis:3.2 - 启动redis容器 docker run -d -p 6379:6379 -v $PWD/redis/data:/data -d --name redis-server redis:3.2 --appendonly yes --requirepass "123456" - 注释 - - -p 6379:6379 => 映射端口6379 - - -v $PWD/redis/data:/data => 将主机中当前目录下的data挂载到容器的/data - - --name redis-server =>容器别名 - - --requirepass "root" => 设置密码为root - - --appendonly yes => 启用AOF持久化方式,设置为no重启数据不会保存 - 进入容器内部测试 进入容器内部 docker exec -it redis-server /bin/bash 连接redis redis-cli 登录redis auth root