接口文档地址:http://212.64.58.72:8081/doc.html
前后端仓库管理系统 采用Springboot、mybatis-plus框架 shiro为安全框架 layuimini为前端框架 redis做缓存 docker容器进行mysql的部署
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.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.缓存问题
10.docker安装redis
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。