parent
8f99e1b247
commit
0d5daac9e8
|
@ -57,4 +57,7 @@ public class CurrentMenu implements Serializable {
|
||||||
this.menuType = menuType;
|
this.menuType = menuType;
|
||||||
this.num = num;
|
this.num = num;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CurrentMenu() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.len.base;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
@ -11,9 +12,7 @@ import lombok.ToString;
|
||||||
* @date 2017/12/30.
|
* @date 2017/12/30.
|
||||||
* @email 154040976@qq.com
|
* @email 154040976@qq.com
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Data
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public class CurrentRole implements Serializable {
|
public class CurrentRole implements Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,4 +31,7 @@ public class CurrentRole implements Serializable {
|
||||||
this.roleName = roleName;
|
this.roleName = roleName;
|
||||||
this.remark = remark;
|
this.remark = remark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CurrentRole() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package com.len;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hello world!
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class App
|
|
||||||
{
|
|
||||||
public static void main( String[] args )
|
|
||||||
{
|
|
||||||
System.out.println( "Hello World!" );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,6 @@ import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.authc.ExcessiveAttemptsException;
|
import org.apache.shiro.authc.ExcessiveAttemptsException;
|
||||||
import org.apache.shiro.authc.IncorrectCredentialsException;
|
import org.apache.shiro.authc.IncorrectCredentialsException;
|
||||||
import org.apache.shiro.authc.UnknownAccountException;
|
import org.apache.shiro.authc.UnknownAccountException;
|
||||||
import org.apache.shiro.session.Session;
|
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
@ -40,10 +39,10 @@ import java.util.List;
|
||||||
public class LoginController {
|
public class LoginController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SysUserService userService;
|
private MenuService menuService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private MenuService menuService;
|
SysUserService userService;
|
||||||
|
|
||||||
@GetMapping(value = "")
|
@GetMapping(value = "")
|
||||||
public String loginInit() {
|
public String loginInit() {
|
||||||
|
@ -66,7 +65,6 @@ public class LoginController {
|
||||||
Subject sub = SecurityUtils.getSubject();
|
Subject sub = SecurityUtils.getSubject();
|
||||||
Boolean flag2 = sub.isRemembered();
|
Boolean flag2 = sub.isRemembered();
|
||||||
boolean flag = sub.isAuthenticated() || flag2;
|
boolean flag = sub.isAuthenticated() || flag2;
|
||||||
Session session = sub.getSession();
|
|
||||||
if (flag) {
|
if (flag) {
|
||||||
return "/main/main";
|
return "/main/main";
|
||||||
}
|
}
|
||||||
|
@ -84,11 +82,11 @@ public class LoginController {
|
||||||
@ApiOperation(value = "/login", httpMethod = "POST", notes = "登录method")
|
@ApiOperation(value = "/login", httpMethod = "POST", notes = "登录method")
|
||||||
@PostMapping(value = "/login")
|
@PostMapping(value = "/login")
|
||||||
public String login(SysUser user, Model model, String rememberMe, HttpServletRequest request) {
|
public String login(SysUser user, Model model, String rememberMe, HttpServletRequest request) {
|
||||||
/*String codeMsg = (String) request.getAttribute("shiroLoginFailure");
|
String codeMsg = (String) request.getAttribute("shiroLoginFailure");
|
||||||
if ("code.error".equals(codeMsg)) {
|
if ("code.error".equals(codeMsg)) {
|
||||||
model.addAttribute("message", "验证码错误");
|
model.addAttribute("message", "验证码错误");
|
||||||
return "/login";
|
return "/login";
|
||||||
}*/
|
}
|
||||||
CustomUsernamePasswordToken token = new CustomUsernamePasswordToken(user.getUsername().trim(),
|
CustomUsernamePasswordToken token = new CustomUsernamePasswordToken(user.getUsername().trim(),
|
||||||
user.getPassword(), "UserLogin");
|
user.getPassword(), "UserLogin");
|
||||||
Subject subject = ShiroUtil.getSubject();
|
Subject subject = ShiroUtil.getSubject();
|
||||||
|
@ -96,6 +94,8 @@ public class LoginController {
|
||||||
try {
|
try {
|
||||||
subject.login(token);
|
subject.login(token);
|
||||||
if (subject.isAuthenticated()) {
|
if (subject.isAuthenticated()) {
|
||||||
|
userService.setMenuAndRoles(token.getUsername());
|
||||||
|
token.getUsername();
|
||||||
return "redirect:/main";
|
return "redirect:/main";
|
||||||
}
|
}
|
||||||
} catch (UnknownAccountException | IncorrectCredentialsException e) {
|
} catch (UnknownAccountException | IncorrectCredentialsException e) {
|
||||||
|
|
|
@ -16,9 +16,6 @@ import com.len.util.BeanUtil;
|
||||||
import com.len.util.JsonUtil;
|
import com.len.util.JsonUtil;
|
||||||
import com.len.util.ReType;
|
import com.len.util.ReType;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -29,6 +26,8 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author zhuxiaomeng
|
* @author zhuxiaomeng
|
||||||
* @date 2017/12/19.
|
* @date 2017/12/19.
|
||||||
|
|
|
@ -97,7 +97,7 @@ public class JobTask {
|
||||||
.withMisfireHandlingInstructionDoNothing();
|
.withMisfireHandlingInstructionDoNothing();
|
||||||
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
|
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey)
|
||||||
.withDescription(createTime).withSchedule(schedBuilder).build();
|
.withDescription(createTime).withSchedule(schedBuilder).build();
|
||||||
Class clazz = null;
|
|
||||||
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
|
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
|
||||||
HashSet<Trigger> triggerSet = new HashSet<>();
|
HashSet<Trigger> triggerSet = new HashSet<>();
|
||||||
triggerSet.add(trigger);
|
triggerSet.add(trigger);
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
package com.len.core.shiro;
|
package com.len.core.shiro;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONArray;
|
|
||||||
import com.len.base.CurrentMenu;
|
import com.len.base.CurrentMenu;
|
||||||
import com.len.base.CurrentRole;
|
import com.len.base.CurrentRole;
|
||||||
import com.len.base.CurrentUser;
|
import com.len.base.CurrentUser;
|
||||||
import com.len.entity.SysMenu;
|
|
||||||
import com.len.entity.SysRole;
|
|
||||||
import com.len.entity.SysUser;
|
import com.len.entity.SysUser;
|
||||||
import com.len.service.MenuService;
|
|
||||||
import com.len.service.RoleMenuService;
|
|
||||||
import com.len.service.RoleUserService;
|
|
||||||
import com.len.service.SysUserService;
|
import com.len.service.SysUserService;
|
||||||
import com.len.util.JWTUtil;
|
import com.len.util.JWTUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -17,15 +11,12 @@ import org.apache.shiro.authc.*;
|
||||||
import org.apache.shiro.authz.AuthorizationInfo;
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||||
import org.apache.shiro.realm.AuthorizingRealm;
|
import org.apache.shiro.realm.AuthorizingRealm;
|
||||||
import org.apache.shiro.session.Session;
|
|
||||||
import org.apache.shiro.subject.PrincipalCollection;
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
import org.apache.shiro.subject.Subject;
|
|
||||||
import org.apache.shiro.util.ByteSource;
|
import org.apache.shiro.util.ByteSource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -40,15 +31,6 @@ public class LoginRealm extends AuthorizingRealm {
|
||||||
@Autowired
|
@Autowired
|
||||||
private SysUserService userService;
|
private SysUserService userService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MenuService menuService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RoleUserService roleUserService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RoleMenuService roleMenuService;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取授权
|
* 获取授权
|
||||||
|
@ -94,7 +76,6 @@ public class LoginRealm extends AuthorizingRealm {
|
||||||
@Override
|
@Override
|
||||||
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
|
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
|
||||||
throws AuthenticationException {
|
throws AuthenticationException {
|
||||||
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
|
|
||||||
String username = (String) authenticationToken.getPrincipal();
|
String username = (String) authenticationToken.getPrincipal();
|
||||||
SysUser s = null;
|
SysUser s = null;
|
||||||
try {
|
try {
|
||||||
|
@ -104,33 +85,6 @@ public class LoginRealm extends AuthorizingRealm {
|
||||||
}
|
}
|
||||||
if (s == null) {
|
if (s == null) {
|
||||||
throw new UnknownAccountException("账户密码不正确");
|
throw new UnknownAccountException("账户密码不正确");
|
||||||
} else {
|
|
||||||
CurrentUser currentUser = new CurrentUser(s.getId(), s.getUsername(), s.getAge(), s.getEmail(), s.getPhoto(), s.getRealName());
|
|
||||||
Subject subject = ShiroUtil.getSubject();
|
|
||||||
/**角色权限封装进去*/
|
|
||||||
//根据用户获取菜单
|
|
||||||
List<SysMenu> menuList = new ArrayList<>(new HashSet<>(menuService.getUserMenu(s.getId())));
|
|
||||||
JSONArray json = menuService.getMenuJsonByUser(menuList);
|
|
||||||
Session session = subject.getSession();
|
|
||||||
session.setAttribute("menu", json);
|
|
||||||
CurrentMenu currentMenu = null;
|
|
||||||
List<CurrentMenu> currentMenuList = new ArrayList<>();
|
|
||||||
List<SysRole> roleList = new ArrayList<>();
|
|
||||||
for (SysMenu m : menuList) {
|
|
||||||
currentMenu = new CurrentMenu(m.getId(), m.getName(), m.getPId(), m.getUrl(), m.getOrderNum(), m.getIcon(), m.getPermission(), m.getMenuType(), m.getNum());
|
|
||||||
currentMenuList.add(currentMenu);
|
|
||||||
roleList.addAll(m.getRoleList());
|
|
||||||
}
|
|
||||||
roleList = new ArrayList<>(new HashSet<>(roleList));
|
|
||||||
List<CurrentRole> currentRoleList = new ArrayList<>();
|
|
||||||
CurrentRole role = null;
|
|
||||||
for (SysRole r : roleList) {
|
|
||||||
role = new CurrentRole(r.getId(), r.getRoleName(), r.getRemark());
|
|
||||||
currentRoleList.add(role);
|
|
||||||
}
|
|
||||||
currentUser.setCurrentRoleList(currentRoleList);
|
|
||||||
currentUser.setCurrentMenuList(currentMenuList);
|
|
||||||
session.setAttribute("curentUser", currentUser);
|
|
||||||
}
|
}
|
||||||
ByteSource byteSource = ByteSource.Util.bytes(username);
|
ByteSource byteSource = ByteSource.Util.bytes(username);
|
||||||
return new SimpleAuthenticationInfo(username, s.getPassword(), byteSource, getName());
|
return new SimpleAuthenticationInfo(username, s.getPassword(), byteSource, getName());
|
||||||
|
|
|
@ -36,11 +36,6 @@ public class RoleServiceImpl extends BaseServiceImpl<SysRole,String> implements
|
||||||
return roleMapper.insert(record);
|
return roleMapper.insert(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @Override
|
|
||||||
public int insertSelective(SysRole record) {
|
|
||||||
return roleMapper.insertSelective(record);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SysRole selectByPrimaryKey(String id) {
|
public SysRole selectByPrimaryKey(String id) {
|
||||||
return roleMapper.selectByPrimaryKey(id);
|
return roleMapper.selectByPrimaryKey(id);
|
||||||
|
|
|
@ -1,28 +1,39 @@
|
||||||
package com.len.service.impl;
|
package com.len.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
import com.len.base.BaseMapper;
|
import com.len.base.BaseMapper;
|
||||||
|
import com.len.base.CurrentMenu;
|
||||||
|
import com.len.base.CurrentRole;
|
||||||
|
import com.len.base.CurrentUser;
|
||||||
import com.len.base.impl.BaseServiceImpl;
|
import com.len.base.impl.BaseServiceImpl;
|
||||||
|
import com.len.core.shiro.ShiroUtil;
|
||||||
|
import com.len.entity.SysMenu;
|
||||||
import com.len.entity.SysRole;
|
import com.len.entity.SysRole;
|
||||||
import com.len.entity.SysRoleUser;
|
import com.len.entity.SysRoleUser;
|
||||||
import com.len.entity.SysUser;
|
import com.len.entity.SysUser;
|
||||||
import com.len.exception.MyException;
|
import com.len.exception.MyException;
|
||||||
import com.len.mapper.SysRoleUserMapper;
|
import com.len.mapper.SysRoleUserMapper;
|
||||||
import com.len.mapper.SysUserMapper;
|
import com.len.mapper.SysUserMapper;
|
||||||
|
import com.len.service.MenuService;
|
||||||
import com.len.service.RoleService;
|
import com.len.service.RoleService;
|
||||||
import com.len.service.RoleUserService;
|
import com.len.service.RoleUserService;
|
||||||
import com.len.service.SysUserService;
|
import com.len.service.SysUserService;
|
||||||
|
import com.len.util.BeanUtil;
|
||||||
import com.len.util.Checkbox;
|
import com.len.util.Checkbox;
|
||||||
import com.len.util.JsonUtil;
|
import com.len.util.JsonUtil;
|
||||||
import com.len.util.Md5Util;
|
import com.len.util.Md5Util;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.shiro.session.Session;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author zhuxiaomeng
|
* @author zhuxiaomeng
|
||||||
* @date 2017/12/4.
|
* @date 2017/12/4.
|
||||||
|
@ -42,6 +53,12 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUser,String> implemen
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
RoleUserService roleUserService;
|
RoleUserService roleUserService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
MenuService menuService;
|
||||||
|
|
||||||
|
private static final String ADMIN = "admin";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseMapper<SysUser, String> getMappser() {
|
public BaseMapper<SysUser, String> getMappser() {
|
||||||
return sysUserMapper;
|
return sysUserMapper;
|
||||||
|
@ -54,7 +71,6 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUser,String> implemen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int deleteByPrimaryKey(String id) {
|
public int deleteByPrimaryKey(String id) {
|
||||||
return sysUserMapper.deleteByPrimaryKey(id);
|
return sysUserMapper.deleteByPrimaryKey(id);
|
||||||
|
@ -95,6 +111,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUser,String> implemen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询
|
* 分页查询
|
||||||
|
*
|
||||||
* @param
|
* @param
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
@ -124,7 +141,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUser,String> implemen
|
||||||
JsonUtil j = new JsonUtil();
|
JsonUtil j = new JsonUtil();
|
||||||
try {
|
try {
|
||||||
SysUser sysUser = selectByPrimaryKey(id);
|
SysUser sysUser = selectByPrimaryKey(id);
|
||||||
if("admin".equals(sysUser.getUsername())){
|
if (ADMIN.equals(sysUser.getUsername())) {
|
||||||
return JsonUtil.error("超管无法删除");
|
return JsonUtil.error("超管无法删除");
|
||||||
}
|
}
|
||||||
SysRoleUser roleUser = new SysRoleUser();
|
SysRoleUser roleUser = new SysRoleUser();
|
||||||
|
@ -162,9 +179,9 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUser,String> implemen
|
||||||
SysRoleUser sysRoleUser = new SysRoleUser();
|
SysRoleUser sysRoleUser = new SysRoleUser();
|
||||||
sysRoleUser.setUserId(id);
|
sysRoleUser.setUserId(id);
|
||||||
List<SysRoleUser> kList = selectByCondition(sysRoleUser);
|
List<SysRoleUser> kList = selectByCondition(sysRoleUser);
|
||||||
System.out.println(kList.size());
|
|
||||||
List<Checkbox> checkboxList = new ArrayList<>();
|
List<Checkbox> checkboxList = new ArrayList<>();
|
||||||
Checkbox checkbox=null;
|
Checkbox checkbox;
|
||||||
for (SysRole sysRole : roleList) {
|
for (SysRole sysRole : roleList) {
|
||||||
checkbox = new Checkbox();
|
checkbox = new Checkbox();
|
||||||
checkbox.setId(sysRole.getId());
|
checkbox.setId(sysRole.getId());
|
||||||
|
@ -172,6 +189,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUser,String> implemen
|
||||||
for (SysRoleUser sysRoleUser1 : kList) {
|
for (SysRoleUser sysRoleUser1 : kList) {
|
||||||
if (sysRoleUser1.getRoleId().equals(sysRole.getId())) {
|
if (sysRoleUser1.getRoleId().equals(sysRole.getId())) {
|
||||||
checkbox.setCheck(true);
|
checkbox.setCheck(true);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkboxList.add(checkbox);
|
checkboxList.add(checkbox);
|
||||||
|
@ -183,24 +201,4 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUser,String> implemen
|
||||||
public int rePass(SysUser user) {
|
public int rePass(SysUser user) {
|
||||||
return sysUserMapper.rePass(user);
|
return sysUserMapper.rePass(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<SysUser> getUserByRoleId(String roleId,int page,int limit)
|
|
||||||
{
|
|
||||||
Map map = new HashMap<>();
|
|
||||||
map.put("roleId",roleId);
|
|
||||||
map.put("page",(page-1)*limit );
|
|
||||||
map.put("limit",limit);
|
|
||||||
return sysUserMapper.getUserByRoleId(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int countUserByRoleId(String roleId,int page,int limit)
|
|
||||||
{
|
|
||||||
Map map = new HashMap<>();
|
|
||||||
map.put("roleId",roleId);
|
|
||||||
map.put("page",(page-1)*limit );
|
|
||||||
map.put("limit",limit);
|
|
||||||
return sysUserMapper.countUserByRoleId(map);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue