1 Star 42 Fork 5

OneSheep123 / 前后端分离仓库管理系统

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

前后端分离仓库管理系统

网址:http://212.64.58.72:82

接口文档地址:http://212.64.58.72:8081/doc.html

介绍

前后端仓库管理系统 采用Springboot、mybatis-plus框架 shiro为安全框架 layuimini为前端框架 redis做缓存 docker容器进行mysql的部署

演示截图

输入图片说明 输入图片说明 输入图片说明 输入图片说明 输入图片说明

安装教程(详细看部署文档)

  1. 将Maven项目导出为jar包
  2. 上传项目到云服务器
  3. 将前端文件夹web放到nginx目录下并改名为erpweb
  4. 放行端口3个端口,例如8881、8882、8883作为项目端口,进行负载均衡
  5. 进入jar包项目,使用'java -jar erp'命令开启三个项目
  6. 放行82端口进行网站访问
  7. 配置nginx.conf文件,在后面加入
upstream www.erp.com {
              server 127.0.0.1:8881;
              server 127.0.0.1:8882;
              server 127.0.0.1:8883;
             #ip_hash;这是是用来解决登陆的session的问题
     }
server
    {
        listen 82;
        server_name localhost;
        root erpweb;
        index login.html;
        location ^~ /api/ {
            proxy_pass http://www.erp.com;
            proxy_send_timeout 1800;
            proxy_read_timeout 1800;
            proxy_connect_timeout 1800;
            client_max_body_size 2048m;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_set_header  Host              $http_host;
            proxy_set_header  X-Real-IP         $remote_addr;
            proxy_set_header  X-Forwarded-For   $proxy_add_x_forwarded_for;
            proxy_set_header  X-Forwarded-Proto $scheme;
        }
        
    }
  1. 重启nginx,即可访问

注意事项

1.shiro登录session共享问题

修改pom.xml引入shiro-redis

<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.2.3</version>
</dependency>

修改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<String, String> 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<DelegatingFilterProxy> filterRegistrationBeanDelegatingFilterProxy() {
        FilterRegistrationBean<DelegatingFilterProxy> bean = new FilterRegistrationBean<>();
        //创建过滤器
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        bean.setFilter(proxy);
        bean.addInitParameter("targetFilterLifecycle", "true");
        bean.addInitParameter("targetBeanName", "shiroFilter");
//        bean.addUrlPatterns();
        List<String> 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.登陆页面修改

<script src="resources/lib/jquery-3.4.1/jquery-3.4.1.min.js" charset="utf-8"></script>
<script src="resources/lib/common/jquery.cookie.min.js"></script>
<script src="resources/lib/common/common.js"></script>

登录按钮
<script>
 // 进行登录操作
    form.on('submit(login)', function (data) {
      var btn = $(this);
      //设置登录按钮  为不可点击
      btn.text("登录中...").attr("disabled", "disabled").addClass("layui-disabled");
      $.post(api + "login/doLogin", {
        "loginname": data.field.loginname,
        "password": data.field.password,
        "captcha": data.field.captcha,
      }, function (rs) {
        //设置登录按钮 恢复可点击   在前端防止 重复点击
        btn.text("登录").attr("disabled", false).removeClass("layui-disabled");
        if (rs.code != 200) {
          layer.msg(rs.msg);
        } else {
            console.log(rs.token);
          //登录成功之后将token写入到cookie中
          $.cookie('TOKEN', rs.token, {expire: 7})
          //跳转到templates/list.html页面
          location.href = "/ERP-WEB/index.html"
        }
      });
      return false;
    });
</script>

3.主页修改

引入js

<script src="resources/lib/jquery-3.4.1/jquery-3.4.1.min.js" charset="utf-8"></script>
<script src="resources/lib/common/jquery.cookie.min.js"></script>
<script src="resources/lib/common/common.js"></script>

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.@Lazy + @Autowird(不要用@Resourse)

加上原因:

Userserviceimpl中加上了@lazy 是因为原来userservice中配置的aop(redis缓存)不生效,不是代理类

原因是realm比userService先执行,导致它的切面没有被注入,而controller不加是因为contoller等到用户调用url时才有用

8.redis做缓存时,注入service与ioc容器生成代理对象顺序问题解决 //问题:deptServiceimpl中对应的getById的 redis切面不生效 原因是:realm导致的

在依赖注入过后,通过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<User> page = new Page<>(userVo.getPage(), userVo.getLimit());
        QueryWrapper<User> 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<User> 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缓存的代理对象

9.缓存问题

  • 问题描述:当使用表格里面是否可用对数据进行更新之后,缓存里面的数据丢失部分 ,原因是因为@CachePut里缓存的是返回的值的对象
  • 解决思路:先进行修改,再进行一次查询,将查询到的数据进行返回

10.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

空文件

简介

前后端仓库管理系统 采用Springboot框架 shiro为安全框架 redis做缓存 docker容器进行mysql的部署 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/OneSheep123/erp.git
git@gitee.com:OneSheep123/erp.git
OneSheep123
erp
前后端分离仓库管理系统
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891