增加管理员模块~

This commit is contained in:
YunaiV
2019-02-27 00:00:37 +08:00
parent e431530107
commit 09004dc000
65 changed files with 1929 additions and 104 deletions

View File

@@ -0,0 +1,14 @@
package cn.iocoder.mall.admin.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@MapperScan("cn.iocoder.mall.admin.dao") // 扫描对应的 Mapper 接口
@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理。为什么使用 proxyTargetClass 参数,参见 https://blog.csdn.net/huang_550/article/details/76492600
public class DatabaseConfiguration {
// 数据源,使用 HikariCP
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.mall.admin.config;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
@Configuration
public class ServiceExceptionConfiguration {
@EventListener(ApplicationReadyEvent.class) // 可参考 https://www.cnblogs.com/ssslinppp/p/7607509.html
public void initMessages() {
// 从 service_exception_message.properties 加载错误码的方案
// Properties properties;
// try {
// properties = PropertiesLoaderUtils.loadAllProperties("classpath:service_exception_message.properties");
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
for (AdminErrorCodeEnum item : AdminErrorCodeEnum.values()) {
ServiceExceptionUtil.put(item.getCode(), item.getMessage());
}
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.mall.admin.convert;
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.admin.api.bo.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
import cn.iocoder.mall.admin.dataobject.OAuth2AccessTokenDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface OAuth2Convert {
OAuth2Convert INSTANCE = Mappers.getMapper(OAuth2Convert.class);
@Mappings({
@Mapping(source = "id", target = "accessToken")
})
OAuth2AccessTokenBO convertToAccessToken(OAuth2AccessTokenDO oauth2AccessTokenDO);
default OAuth2AccessTokenBO convertToAccessTokenWithExpiresIn(OAuth2AccessTokenDO oauth2AccessTokenDO) {
return this.convertToAccessToken(oauth2AccessTokenDO)
.setExpiresIn(Math.max((int) ((oauth2AccessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis()) / 1000), 0));
}
@Mappings({
@Mapping(source = "oauth2AccessTokenDO.id", target = "accessToken"),
@Mapping(source = "adminRoleDOs.roleId", target = "roleIds")
})
OAuth2AuthenticationBO convertToAuthentication(OAuth2AccessTokenDO oauth2AccessTokenDO, List<AdminRoleDO> adminRoleDOs);
}

View File

@@ -0,0 +1,12 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.AdminDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface AdminMapper {
AdminDO selectByUsername(@Param("username") String username);
}

View File

@@ -0,0 +1,14 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AdminRoleMapper {
List<AdminRoleDO> selectByAdminId(@Param("adminId") Integer adminId);
}

View File

@@ -0,0 +1,13 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.OAuth2AccessTokenDO;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2AccessTokenMapper {
void insert(OAuth2AccessTokenDO entity);
OAuth2AccessTokenDO selectByTokenId(String tokenId);
}

View File

@@ -0,0 +1,11 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.OAuth2RefreshTokenDO;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2RefreshTokenMapper {
void insert(OAuth2RefreshTokenDO entity);
}

View File

@@ -0,0 +1,14 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.RoleResourceDO;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface RoleResourceMapper {
List<RoleResourceDO> selectByResourceHandler(@Param("resourceHandler") String resourceHandler);
}

View File

@@ -0,0 +1,100 @@
package cn.iocoder.mall.admin.dataobject;
import java.util.Date;
/**
* 管理员实体
*/
public class AdminDO {
/**
* 账号状态 - 开启
*/
public static final Integer STATUS_ENABLE = 1;
/**
* 账号状态 - 禁用
*/
public static final Integer STATUS_DISABLE = 2;
/**
* 管理员编号
*/
private Integer id;
/**
* 登陆账号
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 密码
*
* TODO 芋艿 暂时最简单的 MD5
*/
private String password;
/**
* 创建时间
*/
private Date createTime;
/**
* 账号状态
*/
private Integer status;
public Integer getId() {
return id;
}
public AdminDO setId(Integer id) {
this.id = id;
return this;
}
public String getUsername() {
return username;
}
public AdminDO setUsername(String username) {
this.username = username;
return this;
}
public String getNickname() {
return nickname;
}
public AdminDO setNickname(String nickname) {
this.nickname = nickname;
return this;
}
public String getPassword() {
return password;
}
public AdminDO setPassword(String password) {
this.password = password;
return this;
}
public Date getCreateTime() {
return createTime;
}
public AdminDO setCreateTime(Date createTime) {
this.createTime = createTime;
return this;
}
public Integer getStatus() {
return status;
}
public AdminDO setStatus(Integer status) {
this.status = status;
return this;
}
}

View File

@@ -0,0 +1,65 @@
package cn.iocoder.mall.admin.dataobject;
import java.util.Date;
/**
* {@link AdminDO} 和 {@link RoleDO} 的关联表
*/
public class AdminRoleDO {
/**
* 编号
*/
private Integer id;
/**
* 管理员编号(外键:{@link AdminDO}
*/
private Integer adminId;
/**
* 角色编号(外键:{@link RoleDO}
*/
private Integer roleId;
/**
* 创建时间
*/
private Date createTime;
// TODO 芋艿 删除状态
public Integer getId() {
return id;
}
public AdminRoleDO setId(Integer id) {
this.id = id;
return this;
}
public Integer getAdminId() {
return adminId;
}
public AdminRoleDO setAdminId(Integer adminId) {
this.adminId = adminId;
return this;
}
public Integer getRoleId() {
return roleId;
}
public AdminRoleDO setRoleId(Integer roleId) {
this.roleId = roleId;
return this;
}
public Date getCreateTime() {
return createTime;
}
public AdminRoleDO setCreateTime(Date createTime) {
this.createTime = createTime;
return this;
}
}

View File

@@ -0,0 +1,86 @@
package cn.iocoder.mall.admin.dataobject;
import java.util.Date;
public class OAuth2AccessTokenDO {
/**
* 访问令牌
*/
private String id;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 管理员比那好
*/
private Integer adminId;
/**
* 过期时间
*/
private Date expiresTime;
/**
* 是否有效
*/
private Boolean valid;
/**
* 创建时间
*/
private Date createTime;
public String getId() {
return id;
}
public OAuth2AccessTokenDO setId(String id) {
this.id = id;
return this;
}
public String getRefreshToken() {
return refreshToken;
}
public OAuth2AccessTokenDO setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}
public Integer getAdminId() {
return adminId;
}
public OAuth2AccessTokenDO setAdminId(Integer adminId) {
this.adminId = adminId;
return this;
}
public Date getExpiresTime() {
return expiresTime;
}
public OAuth2AccessTokenDO setExpiresTime(Date expiresTime) {
this.expiresTime = expiresTime;
return this;
}
public Boolean getValid() {
return valid;
}
public OAuth2AccessTokenDO setValid(Boolean valid) {
this.valid = valid;
return this;
}
public Date getCreateTime() {
return createTime;
}
public OAuth2AccessTokenDO setCreateTime(Date createTime) {
this.createTime = createTime;
return this;
}
}

View File

@@ -0,0 +1,78 @@
package cn.iocoder.mall.admin.dataobject;
import java.util.Date;
/**
* 刷新令牌
*
* idx_uid
*/
public class OAuth2RefreshTokenDO {
/**
* 刷新令牌
*/
private String id;
/**
* 用户编号
*/
private Integer adminId;
/**
* 是否有效
*/
private Boolean valid;
/**
* 过期时间
*/
private Date expiresTime;
/**
* 创建时间
*/
private Date createTime;
public String getId() {
return id;
}
public OAuth2RefreshTokenDO setId(String id) {
this.id = id;
return this;
}
public Integer getAdminId() {
return adminId;
}
public OAuth2RefreshTokenDO setAdminId(Integer adminId) {
this.adminId = adminId;
return this;
}
public Boolean getValid() {
return valid;
}
public OAuth2RefreshTokenDO setValid(Boolean valid) {
this.valid = valid;
return this;
}
public Date getExpiresTime() {
return expiresTime;
}
public OAuth2RefreshTokenDO setExpiresTime(Date expiresTime) {
this.expiresTime = expiresTime;
return this;
}
public Date getCreateTime() {
return createTime;
}
public OAuth2RefreshTokenDO setCreateTime(Date createTime) {
this.createTime = createTime;
return this;
}
}

View File

@@ -0,0 +1,129 @@
package cn.iocoder.mall.admin.dataobject;
import java.util.Date;
/**
* 资源实体
*/
public class ResourceDO {
/**
* 资源类型 - 菜单
*/
public static final Integer TYPE_MENU = 1;
/**
* 资源类型 - 操作
*
* 例如,按钮。
*/
public static final Integer TYPE_OPERATION = 2;
/**
* 资源编号
*/
private Integer id;
/**
* 资源名字
*/
private String name;
/**
* 资源类型
*/
private Integer type;
/**
* 排序
*/
private Integer sort;
/**
* 展示名
*/
private String displayName;
/**
* 添加时间
*/
private Date createTime;
/**
* 父级资源编号(外键:{@link ResourceDO#id})
*/
private Integer pid;
/**
* 操作
*
* 当资源类型为【菜单】时handler 配置为界面 URL ,或者前端组件名
* 当资源类型为【操作】时handler 配置为后端 URL 。举个例子,如果有一个「创建管理员」的表单,那么前端界面上的按钮可以根据这个 url 判断是否展示,后端接收到该 url 的请求时会判断是否有权限。
*/
private String handler;
public Integer getId() {
return id;
}
public ResourceDO setId(Integer id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public ResourceDO setName(String name) {
this.name = name;
return this;
}
public Integer getType() {
return type;
}
public ResourceDO setType(Integer type) {
this.type = type;
return this;
}
public Integer getSort() {
return sort;
}
public ResourceDO setSort(Integer sort) {
this.sort = sort;
return this;
}
public String getDisplayName() {
return displayName;
}
public ResourceDO setDisplayName(String displayName) {
this.displayName = displayName;
return this;
}
public Date getCreateTime() {
return createTime;
}
public ResourceDO setCreateTime(Date createTime) {
this.createTime = createTime;
return this;
}
public Integer getPid() {
return pid;
}
public ResourceDO setPid(Integer pid) {
this.pid = pid;
return this;
}
public String getHandler() {
return handler;
}
public ResourceDO setHandler(String handler) {
this.handler = handler;
return this;
}
}

View File

@@ -0,0 +1,63 @@
package cn.iocoder.mall.admin.dataobject;
import java.util.Date;
/**
* 角色实体
*/
public class RoleDO {
/**
* 账号状态 - 开启
*/
public static final Integer STATUS_ENABLE = 1;
/**
* 账号状态 - 禁用
*/
public static final Integer STATUS_DISABLE = 2;
/**
* 角色编号
*/
private Integer id;
/**
* 角色名
*/
private String name;
/**
* 创建时间
*/
private Date createTime;
/**
* 状态
*/
private Integer status;
public String getName() {
return name;
}
public RoleDO setName(String name) {
this.name = name;
return this;
}
public Date getCreateTime() {
return createTime;
}
public RoleDO setCreateTime(Date createTime) {
this.createTime = createTime;
return this;
}
public Integer getStatus() {
return status;
}
public RoleDO setStatus(Integer status) {
this.status = status;
return this;
}
}

View File

@@ -0,0 +1,65 @@
package cn.iocoder.mall.admin.dataobject;
import java.util.Date;
/**
* {@link RoleDO} 和 {@link ResourceDO} 的关联表
*/
public class RoleResourceDO {
/**
* 编号
*/
private Integer id;
/**
* 角色编号(外键:{@link RoleDO}
*/
private Integer roleId;
/**
* 资源比那好(外键:{@link ResourceDO}
*/
private Integer resourceId;
/**
* 创建时间
*/
private Date createTime;
// TODO 芋艿 删除状态
public Integer getId() {
return id;
}
public RoleResourceDO setId(Integer id) {
this.id = id;
return this;
}
public Integer getRoleId() {
return roleId;
}
public RoleResourceDO setRoleId(Integer roleId) {
this.roleId = roleId;
return this;
}
public Date getCreateTime() {
return createTime;
}
public RoleResourceDO setCreateTime(Date createTime) {
this.createTime = createTime;
return this;
}
public Integer getResourceId() {
return resourceId;
}
public RoleResourceDO setResourceId(Integer resourceId) {
this.resourceId = resourceId;
return this;
}
}

View File

@@ -0,0 +1 @@
package cn.iocoder.mall.admin;

View File

@@ -0,0 +1,50 @@
package cn.iocoder.mall.admin.service;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.admin.api.AdminService;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.dao.AdminMapper;
import cn.iocoder.mall.admin.dao.AdminRoleMapper;
import cn.iocoder.mall.admin.dataobject.AdminDO;
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.util.List;
@Service
@com.alibaba.dubbo.config.annotation.Service
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminMapper adminMapper;
@Autowired
private AdminRoleMapper adminRoleMapper;
public CommonResult<AdminDO> validAdmin(String username, String password) {
AdminDO admin = adminMapper.selectByUsername(username);
// 账号不存在
if (admin == null) {
return ServiceExceptionUtil.error(AdminErrorCodeEnum.ADMIN_USERNAME_NOT_REGISTERED.getCode());
}
// 密码不正确
if (DigestUtils.md5DigestAsHex(password.getBytes()).equals(admin.getPassword())) {
return ServiceExceptionUtil.error(AdminErrorCodeEnum.ADMIN_PASSWORD_ERROR.getCode());
}
// 账号被禁用
if (AdminDO.STATUS_DISABLE.equals(admin.getStatus())) {
return ServiceExceptionUtil.error(AdminErrorCodeEnum.ADMIN_IS_DISABLE.getCode());
}
// 校验成功,返回管理员。并且,去掉一些非关键字段,考虑安全性。
admin.setPassword(null);
admin.setStatus(null);
return CommonResult.success(admin);
}
public List<AdminRoleDO> getAdminRoles(Integer adminId) {
return adminRoleMapper.selectByAdminId(adminId);
}
}

View File

@@ -0,0 +1,123 @@
package cn.iocoder.mall.admin.service;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.admin.api.OAuth2Service;
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
import cn.iocoder.mall.admin.api.bo.OAuth2AuthenticationBO;
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.admin.convert.OAuth2Convert;
import cn.iocoder.mall.admin.dao.OAuth2AccessTokenMapper;
import cn.iocoder.mall.admin.dao.OAuth2RefreshTokenMapper;
import cn.iocoder.mall.admin.dataobject.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
@com.alibaba.dubbo.config.annotation.Service
public class OAuth2ServiceImpl implements OAuth2Service {
/**
* 访问令牌过期时间,单位:毫秒
*/
@Value("${modules.oauth2-code-service.access-token-expire-time-millis}")
private int accessTokenExpireTimeMillis;
/**
* 刷新令牌过期时间,单位:毫秒
*/
@Value("${modules.oauth2-code-service.refresh-token-expire-time-millis}")
private int refreshTokenExpireTimeMillis;
@Autowired
private AdminServiceImpl adminService;
@Autowired
private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
@Autowired
private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
@Autowired
private RoleServiceImpl roleService;
@Override
public CommonResult<OAuth2AccessTokenBO> getAccessToken(String username, String password) {
CommonResult<AdminDO> adminResult = adminService.validAdmin(username, password);
// 校验失败,返回错误结果
if (adminResult.isError()) {
return CommonResult.error(adminResult);
}
AdminDO admin = adminResult.getData();
// 创建刷新令牌
OAuth2RefreshTokenDO oauth2RefreshTokenDO = createOAuth2RefreshToken(admin.getId());
// 创建访问令牌
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(admin.getId(), oauth2RefreshTokenDO.getId());
// 转换返回
return CommonResult.success(OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO));
}
@Override
public CommonResult<OAuth2AuthenticationBO> checkToken(String accessToken) {
OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByTokenId(accessToken);
if (accessTokenDO == null) { // 不存在
return ServiceExceptionUtil.error(AdminErrorCodeEnum.OAUTH_INVALID_TOKEN_NOT_FOUND.getCode());
}
if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
return ServiceExceptionUtil.error(AdminErrorCodeEnum.OAUTH_INVALID_TOKEN_EXPIRED.getCode());
}
if (!accessTokenDO.getValid()) { // 无效
return ServiceExceptionUtil.error(AdminErrorCodeEnum.OAUTH_INVALID_TOKEN_INVALID.getCode());
}
// 获得管理员拥有的角色
List<AdminRoleDO> adminRoleDOs = adminService.getAdminRoles(accessTokenDO.getAdminId());
return CommonResult.success(OAuth2Convert.INSTANCE.convertToAuthentication(accessTokenDO, adminRoleDOs));
}
@Override
public CommonResult<Boolean> checkPermission(Integer adminId, Set<Integer> roleIds, String url) {
// 避免传入的是空集合
if (roleIds == null) {
roleIds = Collections.emptySet();
}
// 校验权限
List<RoleResourceDO> roleResourceDOs = roleService.getRoleByResourceHandler(url);
if (roleResourceDOs.isEmpty()) { // 任何角色,都可以访问
return CommonResult.success(true);
}
for (RoleResourceDO roleResourceDO : roleResourceDOs) {
if (roleIds.contains(roleResourceDO.getId())) {
return CommonResult.success(true);
}
}
// 没有权限,返回错误
return ServiceExceptionUtil.error(AdminErrorCodeEnum.OAUTH_INVALID_PERMISSION.getCode());
}
private OAuth2AccessTokenDO createOAuth2AccessToken(Integer adminId, String refreshToken) {
OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO().setId(generateAccessToken())
.setRefreshToken(refreshToken)
.setAdminId(adminId)
.setExpiresTime(new Date(System.currentTimeMillis() + accessTokenExpireTimeMillis))
.setValid(true);
oauth2AccessTokenMapper.insert(accessToken);
return accessToken;
}
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Integer adminId) {
OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setId(generateRefreshToken())
.setAdminId(adminId)
.setExpiresTime(new Date(System.currentTimeMillis() + refreshTokenExpireTimeMillis))
.setValid(true);
oauth2RefreshTokenMapper.insert(refreshToken);
return refreshToken;
}
private String generateAccessToken() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
private String generateRefreshToken() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.mall.admin.service;
import cn.iocoder.mall.admin.api.RoleService;
import cn.iocoder.mall.admin.dao.RoleResourceMapper;
import cn.iocoder.mall.admin.dataobject.RoleResourceDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@com.alibaba.dubbo.config.annotation.Service
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleResourceMapper roleResourceMapper;
public List<RoleResourceDO> getRoleByResourceHandler(String resourceHandler) {
return roleResourceMapper.selectByResourceHandler(resourceHandler);
}
}