Merge pull request #9 from dibo-software/develop

Develop
This commit is contained in:
Mazc 2019-06-22 16:06:28 +08:00 committed by GitHub
commit db3e1cc37a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1770 additions and 315 deletions

View File

@ -7,57 +7,30 @@
# diboot-v2
diboot 2.0版本项目,实现: diboot-core全新内核 + diboot-devtools代码生成。
### diboot-core: 优化内核
### diboot-core: 精简优化内核
全新精简内核主要实现单表CRUD和多表关联绑定的无SQL实现方案并提供其他常用开发场景的简单封装。
#### 单表CRUD无SQL
> 基于Mybatis-Plus实现Mybatis-Plus具备通用Mapper方案和灵活的查询构造器
#### 多表关联查询无SQL适用于大多数场景拆分成单表查询自动实现结果绑定
> 通过注解实现多数场景下的关联查询无SQL
##### 1. 注解自动绑定元数据(枚举值)的显示值Label
~~~java
@BindMetadata(type="GENDER", field = "gender")
private String genderLabel;
~~~
##### 2. 注解自动绑定其他表的字段
~~~java
// 支持关联条件+附加条件绑定字段
@BindField(entity=Department.class, field="name", condition="department_id=id AND code IS NOT NULL")
private String deptName;
> 通过注解实现多数场景下的关联查询无SQL化自动绑定
##### 1. @BindMetadata 注解自动绑定元数据(枚举值)的显示值Label
##### 2. @BindField 注解自动绑定其他表的字段
##### 3. @BindEntity 注解自动绑定单个其他表实体Entity
##### 4. @BindEntityList 注解自动绑定其他表实体集合List<Entity>
// 支持通过中间表的级联关联绑定字段
@BindField(entity = Organization.class, field="name", condition="this.department_id=department.id AND department.org_id=id")
private String orgName;
~~~
##### 3. 注解自动绑定其他表实体Entity
~~~java
// 支持关联条件+附加条件绑定Entity
@BindEntity(entity = Department.class, condition="department_id=id")
private Department department;
具体请查看: [diboot-core 注解自动绑定多表关联](https://github.com/dibo-software/diboot-v2/tree/master/diboot-core "注解自动绑定多表关联").
// 通过中间表的级联关联绑定Entity支持附加条件
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id AND department.deleted=0")
private Organization organization;
~~~
##### 4. 注解自动绑定其他表实体集合List<Entity>
~~~java
// 支持关联条件+附加条件绑定多个Entity
@BindEntityList(entity = Department.class, condition = "id=parent_id")
private List<Department> children;
// 通过中间表的 多对多关联 绑定Entity支持附加条件
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Role> roleList;
~~~
> 本地运行example需先执行/resources/init-mysql.sql到数据库。
### diboot-devtools 代码生成工具
提供数据库表管理功能
提供CRUD及关联绑定的相关代码生成Entity,VO,Service,Mapper,Controller...
### diboot-shiro 基于RBAC+Shiro的权限认证模块
RBAC的角色权限+基于Shiro的细粒度权限控制
### diboot-example: 示例
各组件使用示例项目
...
> 运行example需先执行/resources/init-mysql.sql到数据库。
### diboot-devtools 代码生成工具
> 比 1.x 版本更强大的代码生成工具 ...
...

46
diboot-core/README.md Normal file
View File

@ -0,0 +1,46 @@
## diboot-core: 全新优化内核
全新精简内核,主要实现:
1. 多表关联的自动绑定, 实现单表CRUD和多表关联的无SQL化
2. 提供其他常用开发场景的简单封装。
#### 单表CRUD无SQL
> 基于Mybatis-Plus实现Mybatis-Plus具备通用Mapper方案和灵活的查询构造器
#### 多表关联查询无SQL适用于大多数场景拆分成单表查询自动实现结果绑定
> 通过注解实现多数场景下的关联查询无SQL
##### 1. 注解自动绑定元数据(枚举值)的显示值Label
~~~java
@BindMetadata(type="GENDER", field = "gender")
private String genderLabel;
~~~
##### 2. 注解自动绑定其他表的字段
~~~java
// 支持关联条件+附加条件绑定字段
@BindField(entity=Department.class, field="name", condition="department_id=id AND parent_id>=0")
private String deptName;
// 支持通过中间表的级联关联绑定字段
@BindField(entity = Organization.class, field="name", condition="this.department_id=department.id AND department.org_id=id")
private String orgName;
~~~
##### 3. 注解自动绑定其他表实体Entity
~~~java
// 支持关联条件+附加条件绑定Entity
@BindEntity(entity = Department.class, condition="department_id=id")
private Department department;
// 通过中间表的级联关联绑定Entity支持附加条件
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id AND department.deleted=0")
private Organization organization;
~~~
##### 4. 注解自动绑定其他表实体集合List<Entity>
~~~java
// 支持关联条件+附加条件绑定多个Entity
@BindEntityList(entity = Department.class, condition = "id=parent_id")
private List<Department> children;
// 通过中间表的 多对多关联 绑定Entity支持附加条件
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Role> roleList;
~~~
> 本地运行test单元测试需先执行/test/resources/init-{db}.sql到你的数据库暂提供Mysql脚本

View File

@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 基础CRUD的父类Mapper
* @author Mazhicheng
* @version 2018/12/22
* Copyright © www.dibo.ltd
* @version v2.0
* @date 2018/12/22
*/
public interface BaseCrudMapper<T> extends BaseMapper<T> {

View File

@ -5,8 +5,8 @@ import com.diboot.core.entity.Metadata;
/**
* 元数据Mapper
* @author Mazhicheng
* @version 2018/12/22
* Copyright © www.dibo.ltd
* @version v2.0
* @date 2018/12/22
*/
public interface MetadataMapper extends BaseCrudMapper<Metadata> {

View File

@ -1,6 +1,7 @@
package com.diboot.core.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.diboot.core.binding.EntityBinder;
import com.diboot.core.binding.EntityListBinder;
import com.diboot.core.binding.FieldBinder;
@ -18,7 +19,7 @@ import java.util.Map;
* @version 2.0
* @date 2019/01/01
*/
public interface BaseService<T>{
public interface BaseService<T> {
/**
* 获取Entity实体

View File

@ -12,7 +12,6 @@ import com.diboot.core.config.BaseConfig;
import com.diboot.core.config.Cons;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.core.service.BaseService;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.vo.KeyValue;

View File

@ -22,7 +22,21 @@ public class V {
* @return
*/
public static boolean isEmpty(Object obj){
return obj == null;
if(obj instanceof String){
return isEmpty((String)obj);
}
else if(obj instanceof Collection){
return isEmpty((Collection)obj);
}
else if(obj instanceof Map){
return isEmpty((Map)obj);
}
else if(obj instanceof String[]){
return isEmpty((String[])obj);
}
else{
return obj == null;
}
}
/***
@ -67,7 +81,21 @@ public class V {
* @return
*/
public static boolean notEmpty(Object obj){
return obj != null;
if(obj instanceof String){
return notEmpty((String)obj);
}
else if(obj instanceof Collection){
return notEmpty((Collection)obj);
}
else if(obj instanceof Map){
return notEmpty((Map)obj);
}
else if(obj instanceof String[]){
return notEmpty((String[])obj);
}
else{
return obj != null;
}
}
/***

View File

@ -0,0 +1,54 @@
package diboot.core.test.binder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.binding.manager.AnnotationBindingManager;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
import diboot.core.test.StartupApplication;
import diboot.core.test.binder.entity.User;
import diboot.core.test.binder.service.UserService;
import diboot.core.test.binder.vo.EntityBinderVO;
import diboot.core.test.binder.vo.FieldBinderVO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* 测试字段绑定
* @author Mazhicheng
* @version v2.0
* @date 2019/06/22
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {StartupApplication.class})
public class TestEntityBinder {
@Autowired
UserService userService;
@Test
public void testBinder(){
// 加载测试数据
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.getEntityList(queryWrapper);
// 自动绑定
List<EntityBinderVO> voList = AnnotationBindingManager.autoConvertAndBind(userList, EntityBinderVO.class);
// 验证绑定结果
if(V.notEmpty(voList)){
for(EntityBinderVO vo : voList){
// 验证直接关联和通过中间表间接关联的绑定
Assert.assertEquals(vo.getDepartmentId(), vo.getDepartment().getId());
Assert.assertNotNull(vo.getDepartment().getOrgId());
Assert.assertNotNull(vo.getOrganization());
System.out.println(JSON.stringify(vo));
}
}
}
}

View File

@ -0,0 +1,83 @@
package diboot.core.test.binder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.binding.manager.AnnotationBindingManager;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
import diboot.core.test.StartupApplication;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.User;
import diboot.core.test.binder.service.DepartmentService;
import diboot.core.test.binder.service.UserService;
import diboot.core.test.binder.vo.EntityBinderVO;
import diboot.core.test.binder.vo.EntityListComplexBinderVO;
import diboot.core.test.binder.vo.EntityListSimpleBinderVO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* 测试字段绑定
* @author Mazhicheng
* @version v2.0
* @date 2019/06/22
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {StartupApplication.class})
public class TestEntityListBinder {
@Autowired
UserService userService;
@Autowired
DepartmentService departmentService;
/**
* 验证直接关联的绑定
*/
@Test
public void testSimpleBinder(){
// 加载测试数据
LambdaQueryWrapper<Department> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Department::getId, 10001L);
List<Department> entityList = departmentService.getEntityList(queryWrapper);
// 自动绑定
List<EntityListSimpleBinderVO> voList = AnnotationBindingManager.autoConvertAndBind(entityList, EntityListSimpleBinderVO.class);
// 验证绑定结果
if(V.notEmpty(voList)){
for(EntityListSimpleBinderVO vo : voList){
// 验证直接关联的绑定
Assert.assertTrue(V.notEmpty(vo.getChildren()));
System.out.println(JSON.stringify(vo));
}
}
}
/**
* 验证通过中间表间接关联的绑定
*/
@Test
public void testComplexBinder(){
// 加载测试数据
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.getEntityList(queryWrapper);
// 自动绑定
List<EntityListComplexBinderVO> voList = AnnotationBindingManager.autoConvertAndBind(userList, EntityListComplexBinderVO.class);
// 验证绑定结果
if(V.notEmpty(voList)){
for(EntityListComplexBinderVO vo : voList){
// 验证通过中间表间接关联的绑定
Assert.assertTrue(V.notEmpty(vo.getRoleList()));
System.out.println(JSON.stringify(vo));
}
}
}
}

View File

@ -0,0 +1,57 @@
package diboot.core.test.binder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.binding.manager.AnnotationBindingManager;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
import com.diboot.core.vo.Pagination;
import diboot.core.test.StartupApplication;
import diboot.core.test.binder.entity.User;
import diboot.core.test.binder.service.UserService;
import diboot.core.test.binder.vo.FieldBinderVO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* 测试字段绑定
* @author Mazhicheng
* @version v2.0
* @date 2019/06/22
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {StartupApplication.class})
public class TestFieldBinder {
@Autowired
UserService userService;
@Test
public void testBinder(){
// 加载测试数据
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(User::getId, 1001L, 1002L);
List<User> userList = userService.getEntityList(queryWrapper);
// 自动绑定
List<FieldBinderVO> voList = AnnotationBindingManager.autoConvertAndBind(userList, FieldBinderVO.class);
// 验证绑定结果
if(V.notEmpty(voList)){
for(FieldBinderVO vo : voList){
// 验证直接关联和通过中间表间接关联的绑定
Assert.assertNotNull(vo.getDeptName());
Assert.assertNotNull(vo.getOrgName());
Assert.assertNotNull(vo.getOrgTelphone());
// 验证枚举值已绑定
Assert.assertNotNull(vo.getGenderLabel());
System.out.println(JSON.stringify(vo));
}
}
}
}

View File

@ -0,0 +1,47 @@
package diboot.core.test.binder.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.entity.BaseEntity;
/**
* 定时任务
* @author Mazhicheng
* @version v2.0
* @date 2018/12/27
*/
public class Department extends BaseEntity {
private static final long serialVersionUID = -4849732665419794547L;
@TableField
private Long parentId;
@TableField
private Long orgId;
@TableField
private String name;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Long getOrgId() {
return orgId;
}
public void setOrgId(Long orgId) {
this.orgId = orgId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,47 @@
package diboot.core.test.binder.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.entity.BaseEntity;
/**
* 单位Entity
* @author Mazhicheng
* @version v2.0
* @date 2019/1/5
*/
public class Organization extends BaseEntity {
private static final long serialVersionUID = -5889309041570465909L;
@TableField
private Long parentId;
@TableField
private String name;
@TableField
private String telphone;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTelphone() {
return telphone;
}
public void setTelphone(String telphone) {
this.telphone = telphone;
}
}

View File

@ -0,0 +1,48 @@
package diboot.core.test.binder.entity;
import com.alibaba.fastjson.annotation.JSONField;
import com.diboot.core.entity.BaseEntity;
import java.util.Date;
/**
* @author Mazhicheng
* @version v2.0
* @date 2019/1/30
*/
public class Role extends BaseEntity {
private static final long serialVersionUID = 3701095453152116088L;
private String name;
private String code;
@JSONField(serialize = false)
public Date createTime;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public Date getCreateTime() {
return createTime;
}
@Override
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}

View File

@ -0,0 +1,46 @@
package diboot.core.test.binder.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.entity.BaseEntity;
/**
* @author Mazhicheng
* @version v2.0
* @date 2019/1/30
*/
public class User extends BaseEntity {
private static final long serialVersionUID = 3050761344045195972L;
@TableField
private Long departmentId;
@TableField
private String username;
@TableField
private String gender;
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}

View File

@ -0,0 +1,14 @@
package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.Department;
/**
* 部门Mapper
* @author Mazhicheng
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
public interface DepartmentMapper extends BaseCrudMapper<Department> {
}

View File

@ -0,0 +1,15 @@
package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.Organization;
/**
* 单位Mapper
* @author Mazhicheng
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
public interface OrganizationMapper extends BaseCrudMapper<Organization> {
}

View File

@ -0,0 +1,16 @@
package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.Role;
import diboot.core.test.binder.entity.User;
/**
* 员工Mapper
* @author Mazhicheng
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
public interface RoleMapper extends BaseCrudMapper<Role> {
}

View File

@ -0,0 +1,15 @@
package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.User;
/**
* 员工Mapper
* @author Mazhicheng
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
public interface UserMapper extends BaseCrudMapper<User> {
}

View File

@ -0,0 +1,14 @@
package diboot.core.test.binder.service;
import com.diboot.core.service.BaseService;
import diboot.core.test.binder.entity.Department;
/**
* 部门相关Service
* @author Mazhicheng
* @version v2.0
* @date 2019/1/30
*/
public interface DepartmentService extends BaseService<Department> {
}

View File

@ -0,0 +1,14 @@
package diboot.core.test.binder.service;
import com.diboot.core.service.BaseService;
import diboot.core.test.binder.entity.Organization;
/**
* 单位相关Service
* @author Mazhicheng
* @version v2.0
* @date 2019/1/5
*/
public interface OrganizationService extends BaseService<Organization> {
}

View File

@ -0,0 +1,15 @@
package diboot.core.test.binder.service;
import com.diboot.core.service.BaseService;
import diboot.core.test.binder.entity.Role;
import diboot.core.test.binder.entity.User;
/**
* 角色相关Service
* @author Mazhicheng
* @version v2.0
* @date 2019/1/5
*/
public interface RoleService extends BaseService<Role> {
}

View File

@ -0,0 +1,14 @@
package diboot.core.test.binder.service;
import com.diboot.core.service.BaseService;
import diboot.core.test.binder.entity.User;
/**
* 员工相关Service
* @author Mazhicheng
* @version v2.0
* @date 2019/1/5
*/
public interface UserService extends BaseService<User> {
}

View File

@ -0,0 +1,18 @@
package diboot.core.test.binder.service.impl;
import com.diboot.core.service.impl.BaseServiceImpl;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.mapper.DepartmentMapper;
import diboot.core.test.binder.service.DepartmentService;
import org.springframework.stereotype.Service;
/**
* 部门相关Service实现
* @author Mazhicheng
* @version v2.0
* @date 2019/1/30
*/
@Service
public class DepartmentServiceImpl extends BaseServiceImpl<DepartmentMapper, Department> implements DepartmentService {
}

View File

@ -0,0 +1,18 @@
package diboot.core.test.binder.service.impl;
import com.diboot.core.service.impl.BaseServiceImpl;
import diboot.core.test.binder.entity.Organization;
import diboot.core.test.binder.mapper.OrganizationMapper;
import diboot.core.test.binder.service.OrganizationService;
import org.springframework.stereotype.Service;
/**
* 单位相关Service实现
* @author Mazhicheng
* @version 2018/12/23
* Copyright © www.dibo.ltd
*/
@Service
public class OrganizationServiceImpl extends BaseServiceImpl<OrganizationMapper, Organization> implements OrganizationService {
}

View File

@ -0,0 +1,18 @@
package diboot.core.test.binder.service.impl;
import com.diboot.core.service.impl.BaseServiceImpl;
import diboot.core.test.binder.entity.Role;
import diboot.core.test.binder.mapper.RoleMapper;
import diboot.core.test.binder.service.RoleService;
import org.springframework.stereotype.Service;
/**
* 员工相关Service
* @author Mazhicheng
* @version 2018/12/23
* Copyright © www.dibo.ltd
*/
@Service
public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, Role> implements RoleService {
}

View File

@ -0,0 +1,18 @@
package diboot.core.test.binder.service.impl;
import com.diboot.core.service.impl.BaseServiceImpl;
import diboot.core.test.binder.entity.User;
import diboot.core.test.binder.mapper.UserMapper;
import diboot.core.test.binder.service.UserService;
import org.springframework.stereotype.Service;
/**
* 员工相关Service
* @author Mazhicheng
* @version 2018/12/23
* Copyright © www.dibo.ltd
*/
@Service
public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implements UserService {
}

View File

@ -0,0 +1,37 @@
package diboot.core.test.binder.vo;
import com.diboot.core.binding.annotation.BindEntity;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.Organization;
import diboot.core.test.binder.entity.User;
/**
* @author Mazhicheng
* @version v2.0
* @date 2019/1/30
*/
public class EntityBinderVO extends User {
private static final long serialVersionUID = 3526115343377985725L;
// 字段关联相同条件的entity+condition将合并为一条SQL查询
@BindEntity(entity= Department.class, condition="this.department_id=id AND parent_id >= 0")
private Department department;
// 通过中间表关联Entity
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id") // AND ...
private Organization organization;
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public Organization getOrganization() {
return organization;
}
public void setOrganization(Organization organization) {
this.organization = organization;
}
}

View File

@ -0,0 +1,29 @@
package diboot.core.test.binder.vo;
import com.diboot.core.binding.annotation.BindEntityList;
import diboot.core.test.binder.entity.Role;
import diboot.core.test.binder.entity.User;
import java.util.List;
/**
* <Description>
*
* @author Mazhicheng
* @version v2.0
* @date 2019/06/22
*/
public class EntityListComplexBinderVO extends User {
// 支持通过中间表的多-多Entity实体关联
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Role> roleList;
public List<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
}

View File

@ -0,0 +1,27 @@
package diboot.core.test.binder.vo;
import com.diboot.core.binding.annotation.BindEntityList;
import diboot.core.test.binder.entity.Department;
import java.util.List;
/**
* @author Mazhicheng
* @version v2.0
* @date 2019/1/5
*/
public class EntityListSimpleBinderVO extends Department {
private static final long serialVersionUID = -362116388664907913L;
// 直接关联多个Entity
@BindEntityList(entity = Department.class, condition = "this.id=parent_id")
private List<Department> children;
public List<Department> getChildren() {
return children;
}
public void setChildren(List<Department> children) {
this.children = children;
}
}

View File

@ -0,0 +1,60 @@
package diboot.core.test.binder.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.binding.annotation.BindField;
import com.diboot.core.binding.annotation.BindMetadata;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.Organization;
import diboot.core.test.binder.entity.User;
/**
* <Description>
*
* @author Mazhicheng
* @version v2.0
* @date 2019/06/22
*/
public class FieldBinderVO extends User{
private static final long serialVersionUID = 3526115343377985725L;
// 字段关联相同条件的entity+condition将合并为一条SQL查询
@BindField(entity= Department.class, field="name", condition="this.department_id=id AND parent_id IS NOT NULL")
private String deptName;
// 支持级联字段关联相同条件的entity+condition将合并为一条SQL查询
@BindField(entity = Organization.class, field="name", condition="this.department_id=department.id AND department.org_id=id")
private String orgName;
@BindField(entity = Organization.class, field="telphone", condition="this.department_id=department.id AND department.org_id=id")
private String orgTelphone;
// 绑定元数据枚举
@BindMetadata(type="GENDER", field = "gender")
private String genderLabel;
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getOrgName() {
return orgName;
}
public void setOrgName(String orgName) {
this.orgName = orgName;
}
public String getOrgTelphone() {
return orgTelphone;
}
public void setOrgTelphone(String orgTelphone) {
this.orgTelphone = orgTelphone;
}
public String getGenderLabel() {
return genderLabel;
}
public void setGenderLabel(String genderLabel) {
this.genderLabel = genderLabel;
}
}

View File

@ -32,8 +32,8 @@ import java.util.List;
@Configuration
@EnableAutoConfiguration
@EnableTransactionManagement(proxyTargetClass=true)
@ComponentScan(basePackages={"com.diboot"})
@MapperScan({"com.diboot.**.mapper"})
@ComponentScan(basePackages={"com.diboot", "diboot.core"})
@MapperScan({"com.diboot.**.mapper", "diboot.**.mapper"})
public class SpringMvcConfig implements WebMvcConfigurer{
private static final Logger log = LoggerFactory.getLogger(SpringMvcConfig.class);

View File

@ -0,0 +1,87 @@
-- create schema diboot_example collate utf8_general_ci;
-- 初始化表
create table department
(
id bigint unsigned not null comment 'ID'
primary key,
parent_id bigint default 0 not null comment '上级部门ID',
org_id bigint not null comment '单位ID',
name varchar(50) not null comment '名称',
extdata varchar(100) null comment '扩展字段',
deleted tinyint(1) default 0 not null comment '已删除',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间'
)
comment '组织单位' charset=utf8mb4;
create table metadata
(
id int unsigned auto_increment comment 'ID'
primary key,
parent_id int unsigned not null comment '父ID',
type varchar(50) not null comment '元数据类型',
item_name varchar(100) not null comment '元数据项显示名',
item_value varchar(100) null comment '元数据项存储值',
comment varchar(200) null comment '备注',
extdata varchar(200) null comment '扩展属性',
sort_id smallint(6) default 99 not null comment '排序号',
`system` tinyint(1) default 0 not null comment '是否是系统预置',
editable tinyint(1) default 1 not null comment '是否可编辑',
deleted tinyint(1) default 0 not null comment '已删除',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间'
);
create table organization
(
id int auto_increment comment 'ID'
primary key,
parent_id int default 0 not null comment '上级单位ID',
name varchar(100) not null comment '名称',
telphone varchar(20) null comment '电话',
deleted tinyint(1) default 0 not null comment '是否有效',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间'
)
comment '组织单位' charset=utf8mb4;
create table role
(
id int auto_increment comment 'ID'
primary key,
name varchar(20) null,
code varchar(20) null,
deleted tinyint(1) default 0 null,
create_time timestamp default CURRENT_TIMESTAMP null comment '创建时间'
);
create table user
(
id int auto_increment comment 'ID'
primary key,
department_id int default 0 not null,
username varchar(20) null,
gender varchar(20) null,
deleted tinyint(1) default 0 null,
create_time timestamp default CURRENT_TIMESTAMP null comment '创建时间'
);
create table user_role
(
user_id int not null comment '用户ID',
role_id int not null comment '角色ID',
primary key (user_id, role_id)
);
-- 初始化样例数据
INSERT INTO department (id, parent_id, org_id, name)
VALUES (10001, 0, 100001, '产品部'), (10002, 10001, 100001, '研发组'), (10003, 10001, 100001, '测试组');
INSERT INTO metadata (id, parent_id, type, item_name, item_value, comment, extdata, sort_id, `system`, editable)
VALUES (1, 0, 'GENDER', '性别', null, '', null, 99, 1, 1), (2, 1, 'GENDER', '', 'M', null, null, 99, 1, 0), (3, 1, 'GENDER', '', 'F', null, null, 99, 1, 0);
INSERT INTO organization (id, parent_id, name, telphone) VALUES (100001, 0, '苏州帝博', '0512-62988949');
INSERT INTO role (id, name, code) VALUES (101, '管理员', 'ADMIN'), (102, '操作员', 'OPERATOR');
INSERT INTO user (id, department_id, username, gender)
VALUES (1001, 10002, '张三', 'M'), (1002, 10002, '李四', 'F');
INSERT INTO user_role (user_id, role_id) VALUES (1001, 101),(1001, 102),(1002, 102);

2
diboot-example/README.md Normal file
View File

@ -0,0 +1,2 @@
## diboot-example: 各组件/模块的使用样例
本地运行test单元测试需先执行/test/resources/init-{db}.sql到你的数据库暂提供Mysql脚本

View File

@ -2,6 +2,7 @@ dependencies {
compile project(":diboot-core")
compile project(":diboot-shiro")
compile project(":diboot-shiro-wx-mp")
testCompile group: 'junit', name: 'junit', version: '4.12'
}

View File

@ -1,11 +1,18 @@
package com.diboot.example.controller;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.util.V;
import com.diboot.core.vo.JsonResult;
import com.diboot.core.vo.Status;
import com.diboot.shiro.jwt.BaseJwtAuthenticationToken;
import com.diboot.shiro.config.AuthType;
import com.diboot.shiro.entity.SysUser;
import com.diboot.shiro.service.AuthWayService;
import com.diboot.shiro.util.JwtHelper;
import com.diboot.shiro.wx.mp.service.WxMpServiceExt;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
@ -23,9 +30,14 @@ public class AuthTokenController {
private static final Logger logger = LoggerFactory.getLogger(AuthTokenController.class);
private static final String STATE = BaseConfig.getProperty("wechat.state");
@Autowired
private Map<String, AuthWayService> authWayServiceMap;
@Autowired
private WxMpServiceExt wxMpService;
/***
* 用户名密码登录接口
* @param sysUser
@ -57,6 +69,95 @@ public class AuthTokenController {
return new JsonResult(Status.FAIL_INVALID_TOKEN, errorMsg);
}
/**
* 创建OAuth认证链接(为了获取授权所需code)
* @param request
* @return
* @throws Exception
*/
@GetMapping("/buildOAuthUrl")
public JsonResult buildOAuthUrl4mp(HttpServletRequest request) throws Exception{
String url = request.getParameter("url");
if (V.isEmpty(url)){
return new JsonResult(Status.FAIL_OPERATION, new String[]{"url为空获取OAuth链接失败"});
}
String oauthUrl = wxMpService.oauth2buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_USERINFO, STATE);
return new JsonResult(Status.OK, oauthUrl, new String[]{"获取OAuth链接成功"});
}
/**
* 微信公众号的回调授权登录认证
* @param request
* @return
* @throws Exception
*/
@PostMapping("/apply")
public JsonResult applyTokenByOAuth2mp(HttpServletRequest request) throws Exception{
String code = request.getParameter("code");
String state = request.getParameter("state");
String openid = "";
if (JwtHelper.isRequestTokenEffective(request)){
String account = JwtHelper.getAccountFromToken(JwtHelper.getRequestToken(request));
if (account == null){
// 如果有code并且token已过期则使用code获取openid
if (V.isEmpty(code)){
return new JsonResult(Status.FAIL_INVALID_TOKEN, new String[]{"token已过期"});
}
} else {
openid = account;
}
}
// 如果openid没有通过token获取到则通过code获取
if (V.isEmpty(openid)){
// 校验STATE
if (V.notEmpty(STATE) && !STATE.equals(state)){
return new JsonResult(Status.FAIL_OPERATION, new String[]{"非法来源"});
}
// 获取wxMpService
WxMpOAuth2AccessToken wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
if (!wxMpService.oauth2validateAccessToken(wxMpOAuth2AccessToken)){
wxMpOAuth2AccessToken = wxMpService.oauth2refreshAccessToken(wxMpOAuth2AccessToken.getRefreshToken());
}
WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null);
openid = wxMpUser.getOpenId();
}
// 如果没有获取到wechat则返回提示信息
if (V.isEmpty(openid)){
return new JsonResult(Status.FAIL_INVALID_TOKEN, new String[]{"获取信息失败"});
}
// 设置token
BaseJwtAuthenticationToken authToken = new BaseJwtAuthenticationToken(authWayServiceMap, openid, AuthType.WX_MP);
// 获取当前的Subject
Subject subject = SecurityUtils.getSubject();
String token = null;
String errorMsg = null;
try {
subject.login(authToken);
//验证是否登录成功
if(subject.isAuthenticated()){
token = (String)authToken.getCredentials();
logger.debug("openid[" + openid + "]申请token成功authtoken="+token);
}
}
catch(Exception e){
logger.error("登录失败", e);
}
if (V.isEmpty(token)){
String msg = V.notEmpty(errorMsg) ? errorMsg : "申请token失败";
return new JsonResult(Status.FAIL_INVALID_TOKEN, new String[]{msg});
}
return new JsonResult(Status.OK, token, new String[]{"申请token成功"});
}
@PostMapping("/logout")
public JsonResult logout(HttpServletRequest request, HttpServletResponse response) throws Exception{
Subject subject = SecurityUtils.getSubject();
@ -66,4 +167,5 @@ public class AuthTokenController {
return new JsonResult(Status.OK, new String[]{"退出登录成功"});
}
}

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.example.mapper.SysUserMapper">
</mapper>

View File

@ -26,7 +26,7 @@ public class UserVO extends User {
private String genderLabel;
// 字段关联相同条件的entity+condition将合并为一条SQL查询
@BindField(entity=Department.class, field="name", condition="this.department_id=id AND code IS NOT NULL")
@BindField(entity=Department.class, field="name", condition="this.department_id=id AND parent_id IS NOT NULL")
private String deptName;
@BindField(entity=Department.class, field="code", condition="this.department_id=id")
private String deptCode;
@ -42,7 +42,7 @@ public class UserVO extends User {
private Organization organization;
// 支持通过中间表的多-多Entity实体关联
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Role> roleList;
//@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
//private List<Role> roleList;
}

View File

@ -7,7 +7,6 @@ create table department
parent_id bigint default 0 not null comment '上级部门ID',
org_id bigint not null comment '单位ID',
name varchar(50) not null comment '名称',
code varchar(20) null comment '编码',
extdata varchar(100) null comment '扩展字段',
deleted tinyint(1) default 0 not null comment '已删除',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间'
@ -38,7 +37,6 @@ create table organization
parent_id int default 0 not null comment '上级单位ID',
name varchar(100) not null comment '名称',
telphone varchar(20) null comment '电话',
address varchar(255) null comment '地址',
deleted tinyint(1) default 0 not null comment '是否有效',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间'
)
@ -73,13 +71,13 @@ create table user_role
);
-- 初始化样例数据
INSERT INTO department (id, parent_id, org_id, name, code)
VALUES (10001, 0, 100001, '产品部', 'PROD'), (10002, 10001, 100001, '研发组', 'DEV'), (10003, 10001, 100001, '测试组', 'TST');
INSERT INTO department (id, parent_id, org_id, name)
VALUES (10001, 0, 100001, '产品部'), (10002, 10001, 100001, '研发组'), (10003, 10001, 100001, '测试组');
INSERT INTO metadata (id, parent_id, type, item_name, item_value, comment, extdata, sort_id, `system`, editable)
VALUES (1, 0, 'GENDER', '性别', null, '', null, 99, 1, 1), (2, 1, 'GENDER', '', 'M', null, null, 99, 1, 0), (3, 1, 'GENDER', '', 'F', null, null, 99, 1, 0);
INSERT INTO organization (id, parent_id, name, telphone, address) VALUES (100001, 0, '苏州帝博', '0512-62988949', '江苏苏州');
INSERT INTO organization (id, parent_id, name, telphone) VALUES (100001, 0, '苏州帝博', '0512-62988949');
INSERT INTO role (id, name, code) VALUES (101, '管理员', 'ADMIN'), (102, '操作员', 'OPERATOR');

View File

@ -1,9 +0,0 @@
/**
* @author : wee
* @Description: todo
* @Date 2019-05-17 19:15
*/
public class MainTest {
public static void main(String[] args) {
}
}

View File

@ -1,16 +0,0 @@
package com.diboot.example;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {
@Test
public void contextLoads(){
}
}

View File

@ -1,31 +0,0 @@
package com.diboot.example.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.V;
import com.diboot.example.ApplicationTest;
import com.diboot.example.entity.Department;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import static org.junit.Assert.*;
@Component
public class DepartmentServiceTest extends ApplicationTest {
@Autowired
private DepartmentService departmentService;
@Test
public void getEntityListTest() throws Exception{
QueryWrapper<Department> query = new QueryWrapper<>();
query.eq(BeanUtils.convertToFieldName(Department::getName), "研发部");
List<Department> departmentList = departmentService.getEntityList(query);
Assert.assertTrue(V.notEmpty(departmentList));
}
}

23
diboot-shiro-wx-mp/build.gradle Executable file
View File

@ -0,0 +1,23 @@
plugins {
id 'java'
}
group 'com.diboot'
version '2.0-alpha'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile project(":diboot-core")
compile project(":diboot-shiro")
//
compile("com.github.binarywang:weixin-java-mp:3.2.0")
testCompile group: 'junit', name: 'junit', version: '4.12'
}

View File

@ -0,0 +1,20 @@
package com.diboot.shiro.wx.mp.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.diboot.core.entity.BaseEntity;
import lombok.Data;
/***
* @author Wangyl
* @version v2.0
* @date 2019/6/10
*/
@Data
public class WxMpMember extends BaseEntity {
private static final long serialVersionUID = -106928701430810778L;
@TableField
private String openid;
}

View File

@ -0,0 +1,14 @@
package com.diboot.shiro.wx.mp.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.shiro.wx.mp.entity.WxMpMember;
/***
* @author Wangyl
* @version v2.0
* @date 2019/6/10
*/
public interface WxMpMemberMapper extends BaseCrudMapper<WxMpMember> {
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.example.mapper.DepartmentMapper">
<mapper namespace="com.diboot.shiro.wx.mp.mapper.WxMpMemberMapper">
</mapper>

View File

@ -0,0 +1,13 @@
package com.diboot.shiro.wx.mp.service;
import com.diboot.core.service.BaseService;
import com.diboot.shiro.wx.mp.entity.WxMpMember;
/***
* @author Wangyl
* @version v2.0
* @date 2019/6/10
*/
public interface WxMpMemberService extends BaseService<WxMpMember> {
}

View File

@ -0,0 +1,61 @@
package com.diboot.shiro.wx.mp.service;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.util.V;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Map;
@Service
public class WxMpServiceExt extends WxMpServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(WxMpServiceExt.class);
private WxMpMessageRouter wxMpMessageRouter;
private static final String appId = BaseConfig.getProperty("wechat.appId");
private static final String secret = BaseConfig.getProperty("wechat.secret");
private static final String token = BaseConfig.getProperty("wechat.token");
private static final String aesKey = BaseConfig.getProperty("wechat.aesKey");
@PostConstruct
public void init(){
WxMpInMemoryConfigStorage inMemoryConfigStorage = new WxMpInMemoryConfigStorage();
if (V.isEmpty(appId) || V.isEmpty(secret)){
logger.warn("服务号相关的appid或secret为空请检查application.properties配置文件");
}
inMemoryConfigStorage.setAppId(appId);
inMemoryConfigStorage.setSecret(secret);
inMemoryConfigStorage.setToken(token);
inMemoryConfigStorage.setAesKey(aesKey);
setWxMpConfigStorage(inMemoryConfigStorage);
wxMpMessageRouter = new WxMpMessageRouter(this);
wxMpMessageRouter.rule().handler(new WxMpMessageHandler() {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
logger.info("\n接收到 {} 公众号请求消息,内容:{}", wxMpService.getWxMpConfigStorage().getAppId(), wxMpXmlMessage);
return null;
}
}).next();
}
public WxMpMessageRouter getWxMpMessageRouter(){
return wxMpMessageRouter;
}
}

View File

@ -0,0 +1,74 @@
package com.diboot.shiro.wx.mp.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.entity.BaseEntity;
import com.diboot.core.util.V;
import com.diboot.shiro.config.AuthType;
import com.diboot.shiro.jwt.BaseJwtAuthenticationToken;
import com.diboot.shiro.service.AuthWayService;
import com.diboot.shiro.wx.mp.entity.WxMpMember;
import com.diboot.shiro.wx.mp.service.WxMpMemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/***
* 微信公众号认证实现
* @author Wangyl
* @version v2.0
* @date 2019/6/10
*/
@Service
public class WxMpAuthWayServiceImpl implements AuthWayService {
@Autowired
private WxMpMemberService wxMpMemberService;
private AuthType authType = AuthType.WX_MP;
private BaseJwtAuthenticationToken token;
@Override
public AuthType authType() {
return authType;
}
@Override
public void initByToken(BaseJwtAuthenticationToken token) {
this.token = token;
}
@Override
public BaseEntity getUser() {
QueryWrapper<WxMpMember> query = new QueryWrapper();
query.lambda()
.eq(WxMpMember::getOpenid, token.getAccount());
List<WxMpMember> wxMpMemberList = wxMpMemberService.getEntityList(query);
if (V.isEmpty(wxMpMemberList)){
return null;
}
return wxMpMemberList.get(0);
}
@Override
public boolean requirePassword() {
return authType.isRequirePassword();
}
@Override
public boolean isPasswordMatch() {
return true;
}
@Override
public boolean isPreliminaryVerified() {
return false;
}
@Override
public Long getExpiresInMinutes() {
return null;
}
}

View File

@ -0,0 +1,19 @@
package com.diboot.shiro.wx.mp.service.impl;
import com.diboot.core.service.impl.BaseServiceImpl;
import com.diboot.shiro.wx.mp.entity.WxMpMember;
import com.diboot.shiro.wx.mp.mapper.WxMpMemberMapper;
import com.diboot.shiro.wx.mp.service.WxMpMemberService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/***
* @author Wangyl
* @version v2.0
* @date 2019/6/10
*/
@Service
@Slf4j
public class WxMpMemberServiceImpl extends BaseServiceImpl<WxMpMemberMapper, WxMpMember> implements WxMpMemberService {
}

View File

@ -1,25 +0,0 @@
package com.diboot.shiro.bind.annotation;
import java.lang.annotation.*;
/**
* controller上注解用于标记权限的菜单分类
* @author : wee
* @version v2.0
* @Date 2019-06-14 23:00
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PermissionsMenu {
/**
* 菜单编码
* @return
*/
String menuCode();
/**
* 菜单名称
* @return
*/
String menuName();
}

View File

@ -0,0 +1,45 @@
package com.diboot.shiro.bind.annotation;
import java.lang.annotation.*;
/**
* 权限注解的前缀用于controller注解
*
* @author : wee
* @version v 2.0
* @Date 2019-06-17 20:42
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PermissionsPrefix {
/**
* 名称
*
* 设置当前权限前缀名称
* @return
*/
String name();
/**
* 编码
*
* 设置当前权限前缀编码
* @return
*/
String code();
/**
* <h3>{@link RequiresPermissionsWrapper#value()}的前缀</h3>
* <ul>
* <li> value = permissions</li>
* <li>{@link RequiresPermissionsWrapper#value()} = {"list", "get"}</li>
* <li>实际权限为{"permissions:list", "permissions:list"}</li>
* </ul>
* 当前注解优先级低于{@link RequiresPermissionsWrapper#prefix()},
* 如果两者都配置优先使用{@link RequiresPermissionsWrapper#prefix()}
* @return
*/
String prefix() default "";
}

View File

@ -6,7 +6,7 @@ import org.apache.shiro.authz.annotation.RequiresPermissions;
import java.lang.annotation.*;
/**
* 注解{@link RequiresPermissions}增强注解增加权限描述等字段
* 注解{@link RequiresPermissions}包装增加权限描述等字段
* @author : wee
* @version v2.0
* @Date 2019-06-14 17:50
@ -14,35 +14,31 @@ import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@RequiresPermissions("default")
public @interface RequiresPermissionsProxy {
public @interface RequiresPermissionsWrapper {
/**
* 代理 {@link RequiresPermissions#value()}
* 包装 {@link RequiresPermissions#value()}
*/
String[] value();
/**
* 代理 {@link RequiresPermissions#logical()}
* 包装 {@link RequiresPermissions#logical()}
*/
Logical logical() default Logical.AND;
/**
* 菜单编码
* @return
*/
String menuCode();
/**
* 菜单名称
* @return
*/
String menuName();
/**
* 权限名称
* @return
*/
String permissionName();
String name();
/**
* 参照{@link PermissionsPrefix#prefix()}解释
* @return
*/
String prefix() default "";
}

View File

@ -0,0 +1,100 @@
package com.diboot.shiro.bind.aop;
import com.diboot.shiro.bind.aop.PermissionWrapperAnnotationMethodInterceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.authz.aop.*;
import org.apache.shiro.spring.aop.SpringAnnotationResolver;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author : wee
* @version : v2.0
* @Date 2019-06-15 12:07
*/
public class CustomAopAllianceAnnotationsAuthorizingMethodInterceptor extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor {
public CustomAopAllianceAnnotationsAuthorizingMethodInterceptor() {
List<AuthorizingAnnotationMethodInterceptor> interceptors =
new ArrayList<AuthorizingAnnotationMethodInterceptor>(6);
AnnotationResolver resolver = new SpringAnnotationResolver();
interceptors.add(new PermissionWrapperAnnotationMethodInterceptor(resolver));
interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
interceptors.add(new UserAnnotationMethodInterceptor(resolver));
interceptors.add(new GuestAnnotationMethodInterceptor(resolver));
setMethodInterceptors(interceptors);
}
/**
* Creates a {@link MethodInvocation MethodInvocation} that wraps an
* {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} instance,
* enabling Shiro Annotations in <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a> environments
* (Spring, etc).
*
* @param implSpecificMethodInvocation AOP Alliance {@link org.aopalliance.intercept.MethodInvocation MethodInvocation}
* @return a Shiro {@link MethodInvocation MethodInvocation} instance that wraps the AOP Alliance instance.
*/
protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) {
final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation;
return new org.apache.shiro.aop.MethodInvocation() {
@Override
public Method getMethod() {
return mi.getMethod();
}
@Override
public Object[] getArguments() {
return mi.getArguments();
}
@Override
public String toString() {
return "Method invocation [" + mi.getMethod() + "]";
}
@Override
public Object proceed() throws Throwable {
return mi.proceed();
}
@Override
public Object getThis() {
return mi.getThis();
}
};
}
/**
* Simply casts the method argument to an
* {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} and then
* calls <code>methodInvocation.{@link org.aopalliance.intercept.MethodInvocation#proceed proceed}()</code>
*
* @param aopAllianceMethodInvocation the {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation}
* @return the {@link org.aopalliance.intercept.MethodInvocation#proceed() org.aopalliance.intercept.MethodInvocation.proceed()} method call result.
* @throws Throwable if the underlying AOP Alliance <code>proceed()</code> call throws a <code>Throwable</code>.
*/
protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable {
MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation;
return mi.proceed();
}
/**
* Creates a Shiro {@link MethodInvocation MethodInvocation} instance and then immediately calls
* {@link org.apache.shiro.authz.aop.AuthorizingMethodInterceptor#invoke super.invoke}.
*
* @param methodInvocation the AOP Alliance-specific <code>methodInvocation</code> instance.
* @return the return value from invoking the method invocation.
* @throws Throwable if the underlying AOP Alliance method invocation throws a <code>Throwable</code>.
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
return super.invoke(mi);
}
}

View File

@ -0,0 +1,106 @@
package com.diboot.shiro.bind.aop;
import com.diboot.shiro.bind.annotation.RequiresPermissionsWrapper;
import org.apache.shiro.authz.annotation.*;
import org.apache.shiro.mgt.SecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* @author : wee
* @version : v1.0
* @Date 2019-06-15 12:27
*/
public class CustomAuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static final Logger log = LoggerFactory.getLogger(CustomAuthorizationAttributeSourceAdvisor.class);
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
RequiresPermissionsWrapper.class,
RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
};
protected SecurityManager securityManager = null;
/**
* Create a new AuthorizationAttributeSourceAdvisor.
*/
public CustomAuthorizationAttributeSourceAdvisor() {
setAdvice(new CustomAopAllianceAnnotationsAuthorizingMethodInterceptor());
}
public SecurityManager getSecurityManager() {
return securityManager;
}
public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
this.securityManager = securityManager;
}
/**
* Returns <tt>true</tt> if the method or the class has any Shiro annotations, false otherwise.
* The annotations inspected are:
* <ul>
* <li>{@link RequiresPermissionsWrapper RequiresPermissionsWrapper}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles}</li>
* <li>{@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions}</li>
* </ul>
*
* @param method the method to check for a Shiro annotation
* @param targetClass the class potentially declaring Shiro annotations
* @return <tt>true</tt> if the method has a Shiro annotation, false otherwise.
* @see org.springframework.aop.MethodMatcher#matches(java.lang.reflect.Method, Class)
*/
@Override
public boolean matches(Method method, Class targetClass) {
Method m = method;
if ( isAuthzAnnotationPresent(m) ) {
return true;
}
//The 'method' parameter could be from an interface that doesn't have the annotation.
//Check to see if the implementation has it.
if ( targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
} catch (NoSuchMethodException ignored) {
//default return value is false. If we can't find the method, then obviously
//there is no annotation, so just use the default return value.
}
}
return false;
}
private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
if ( a != null ) {
return true;
}
}
return false;
}
private boolean isAuthzAnnotationPresent(Method method) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(method, annClass);
if ( a != null ) {
return true;
}
}
return false;
}
}

View File

@ -1,30 +0,0 @@
package com.diboot.shiro.bind.aop;
import com.diboot.shiro.bind.annotation.RequiresPermissionsProxy;
import com.diboot.shiro.bind.handler.PermissionProxyAnnotationHandler;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor;
/**
* {@link RequiresPermissionsProxy} 拦截器
* @author : wee
* @version : v2.0
* @Date 2019-06-14 22:19
*/
public class PermissionProxyAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
/**
* Default no-argument constructor that ensures this interceptor looks for
* {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotations in a method declaration.
*/
public PermissionProxyAnnotationMethodInterceptor() {
super( new PermissionProxyAnnotationHandler() );
}
/**
* @param resolver
* @since 1.1
*/
public PermissionProxyAnnotationMethodInterceptor(AnnotationResolver resolver) {
super( new PermissionProxyAnnotationHandler(), resolver);
}
}

View File

@ -0,0 +1,54 @@
package com.diboot.shiro.bind.aop;
import com.diboot.shiro.bind.annotation.RequiresPermissionsWrapper;
import com.diboot.shiro.bind.handler.PermissionWrapperAnnotationHandler;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.aop.MethodInvocation;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler;
import org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor;
/**
* {@link RequiresPermissionsWrapper} 拦截器
* @author : wee
* @version : v2.0
* @Date 2019-06-14 22:19
*/
public class PermissionWrapperAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
/**
* Default no-argument constructor that ensures this interceptor looks for
* {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotations in a method declaration.
*/
public PermissionWrapperAnnotationMethodInterceptor() {
super( new PermissionWrapperAnnotationHandler() );
}
/**
* @param resolver
* @since 1.1
*/
public PermissionWrapperAnnotationMethodInterceptor(AnnotationResolver resolver) {
super( new PermissionWrapperAnnotationHandler(), resolver);
}
/**
* 当使用RequiresPermissionsWrapper注解进行权限验证的时候自动的去追加前缀
* @param mi
* @throws AuthorizationException
*/
@Override
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
try {
//默认是直接调用方法上注解现在修改成 获取类和方法上的注解
// ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi));
((PermissionWrapperAnnotationHandler)getHandler()).assertAuthorized(getResolver(), mi);
}
catch(AuthorizationException ae) {
if (ae.getCause() == null) {
ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
}
throw ae;
}
}
}

View File

@ -1,69 +0,0 @@
package com.diboot.shiro.bind.handler;
import com.diboot.shiro.bind.annotation.RequiresPermissionsProxy;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler;
import org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor;
import org.apache.shiro.authz.aop.PermissionAnnotationHandler;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Map;
/**
* {@link RequiresPermissionsProxy} 助手类 参考{@link PermissionAnnotationHandler}实现
* @author : wee
* @version : v2.0
* @Date 2019-06-14 22:19
*/
public class PermissionProxyAnnotationHandler extends AuthorizingAnnotationHandler {
private final static String REQUIRES_PERMISSIONS_VALUE = "value";
private final static String REQUIRES_PERMISSIONS_LOGICAL = "logical";
private final static String JDK_MEMBER_VALUES = "memberValues";
/**
* 标记服务的注解
*/
public PermissionProxyAnnotationHandler() {
super(RequiresPermissionsProxy.class);
}
/**
* {@link RequiresPermissionsProxy} 代理的内容赋值给{@link RequiresPermissions}
*/
@Override
public void assertAuthorized(Annotation a) throws AuthorizationException {
if (!(a instanceof RequiresPermissionsProxy)) {
return;
}
RequiresPermissionsProxy rrAnnotation = (RequiresPermissionsProxy) a;
try {
//获取RequiresPermissionsProxy上的RequiresPermissions注解
RequiresPermissions requiresPermissions = rrAnnotation.annotationType().getAnnotation(RequiresPermissions.class);
InvocationHandler invocationHandler = Proxy.getInvocationHandler(requiresPermissions);
/* memberValues 为JDK中存储所有成员变量值的Map {@link AnnotationInvocationHandler#memberValues}*/
Field jdkValue = invocationHandler.getClass().getDeclaredField(JDK_MEMBER_VALUES);
jdkValue.setAccessible(true);
/*获取RequiresPermissions对应的代理属性值*/
Map<String, Object> memberValues = (Map<String, Object>) jdkValue.get(invocationHandler);
/*动态设置RequiresPermissions注解的内容*/
memberValues.put(REQUIRES_PERMISSIONS_VALUE, rrAnnotation.value());
memberValues.put(REQUIRES_PERMISSIONS_LOGICAL, rrAnnotation.logical());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,164 @@
package com.diboot.shiro.bind.handler;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.shiro.bind.annotation.PermissionsPrefix;
import com.diboot.shiro.bind.annotation.RequiresPermissionsWrapper;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.aop.MethodInvocation;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler;
import org.apache.shiro.authz.aop.PermissionAnnotationHandler;
import org.apache.shiro.subject.Subject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Map;
/**
* {@link RequiresPermissionsWrapper} 助手类 参考{@link PermissionAnnotationHandler}实现
* @author : wee
* @version : v2.0
* @Date 2019-06-14 22:19
*/
public class PermissionWrapperAnnotationHandler extends AuthorizingAnnotationHandler {
private final static String REQUIRES_PERMISSIONS_VALUE = "value";
private final static String REQUIRES_PERMISSIONS_LOGICAL = "logical";
private final static String JDK_MEMBER_VALUES = "memberValues";
/**
* 标记服务的注解
*/
public PermissionWrapperAnnotationHandler() {
super(RequiresPermissionsWrapper.class);
}
/**
* Returns the annotation {@link RequiresPermissions#value value}, from which the Permission will be constructed.
*
* @param a the RequiresPermissions annotation being inspected.
* @return the annotation's <code>value</code>, from which the Permission will be constructed.
*/
protected String[] getAnnotationValue(Annotation a) {
RequiresPermissionsWrapper rpAnnotation = (RequiresPermissionsWrapper) a;
return rpAnnotation.value();
}
/**
* 校验注解{@link RequiresPermissionsWrapper}
*/
@Override
public void assertAuthorized(Annotation a) throws AuthorizationException {
if (!(a instanceof RequiresPermissionsWrapper)) {
return;
}
RequiresPermissionsWrapper rppAnnotation = (RequiresPermissionsWrapper) a;
String[] perms = getAnnotationValue(a);
Subject subject = getSubject();
if (perms.length == 1) {
subject.checkPermission(perms[0]);
return;
}
if (Logical.AND.equals(rppAnnotation.logical())) {
getSubject().checkPermissions(perms);
return;
}
if (Logical.OR.equals(rppAnnotation.logical())) {
boolean hasAtLeastOnePermission = false;
for (String permission : perms) {
if (getSubject().isPermitted(permission)) {
hasAtLeastOnePermission = true;
}
}
if (!hasAtLeastOnePermission) {
getSubject().checkPermission(perms[0]);
}
}
}
/**
* 校验注解{@link RequiresPermissionsWrapper}
*/
public void assertAuthorized(AnnotationResolver resolver, MethodInvocation mi) throws AuthorizationException {
//如果方法上存在RequiresPermissionsWrapper注解那么resolver.getAnnotation()获取的是RequiresPermissionsWrapper注解
//优先从缓存读取
RequiresPermissionsWrapper requiresPermissionsWrapper = (RequiresPermissionsWrapper)resolver.getAnnotation(mi, RequiresPermissionsWrapper.class);
String prefix = "";
if (V.notEmpty(requiresPermissionsWrapper.prefix())) {
prefix = requiresPermissionsWrapper.prefix();
} else {
//如果自身不定义查找前缀注解存在则设置值
PermissionsPrefix permissionsPrefix = (PermissionsPrefix)resolver.getAnnotation(mi, PermissionsPrefix.class);
if (V.notEmpty(permissionsPrefix)) {
prefix = permissionsPrefix.prefix();
}
}
String[] perms = getAnnotationValue(requiresPermissionsWrapper);
Subject subject = getSubject();
String [] permsTemp = new String[perms.length];
//前缀存在的时候才做组装其他情况不处理
if (V.notEmpty(prefix)) {
for (int i = 0; i < perms.length; i++) {
permsTemp[i] = S.join(prefix, ":", perms[i]);
}
perms = permsTemp;
}
if (perms.length == 1) {
subject.checkPermission(perms[0]);
return;
}
if (Logical.AND.equals(requiresPermissionsWrapper.logical())) {
getSubject().checkPermissions(perms);
return;
}
if (Logical.OR.equals(requiresPermissionsWrapper.logical())) {
boolean hasAtLeastOnePermission = false;
for (String permission : perms) {
if (getSubject().isPermitted(permission)) {
hasAtLeastOnePermission = true;
}
}
if (!hasAtLeastOnePermission) {
getSubject().checkPermission(perms[0]);
}
}
}
/**
* 动态修改注解的值不可用
* @param rppAnnotation
*/
@Deprecated
private void proxy(RequiresPermissionsWrapper rppAnnotation) {
try {
//获取RequiresPermissionsProxy上的RequiresPermissions注解
RequiresPermissions requiresPermissions = rppAnnotation.annotationType().getAnnotation(RequiresPermissions.class);
InvocationHandler invocationHandler = Proxy.getInvocationHandler(requiresPermissions);
/* memberValues 为JDK中存储所有成员变量值的Map {@link AnnotationInvocationHandler#memberValues}*/
Field jdkValue = invocationHandler.getClass().getDeclaredField(JDK_MEMBER_VALUES);
jdkValue.setAccessible(true);
/*获取RequiresPermissions对应的代理属性值*/
Map<String, Object> memberValues = (Map<String, Object>) jdkValue.get(invocationHandler);
/*动态设置RequiresPermissions注解的内容*/
memberValues.put(REQUIRES_PERMISSIONS_VALUE, rppAnnotation.value());
memberValues.put(REQUIRES_PERMISSIONS_LOGICAL, rppAnnotation.logical());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

View File

@ -1,16 +1,11 @@
package com.diboot.shiro.config;
import com.diboot.shiro.bind.aop.PermissionProxyAnnotationMethodInterceptor;
import com.diboot.shiro.bind.aop.CustomAuthorizationAttributeSourceAdvisor;
import com.diboot.shiro.jwt.BaseJwtAuthenticationFilter;
import com.diboot.shiro.jwt.BaseJwtRealm;
import org.apache.shiro.aop.AnnotationResolver;
import org.apache.shiro.authz.aop.*;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.aop.SpringAnnotationResolver;
import org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
@ -22,9 +17,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/***
@ -60,16 +53,7 @@ public class ShiroConfig {
//用户访问未对其授权的资源时的错误提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/auth/login", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/auth/logout", "logout");
filterChainDefinitionMap.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinition());
// 设置过滤器
Map<String, Filter> filters = new LinkedHashMap<>();
@ -80,6 +64,22 @@ public class ShiroConfig {
return shiroFilterFactoryBean;
}
@Bean
public Map filterChainDefinition(){
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/auth/login", "anon");
filterChainDefinitionMap.put("/auth/buildOAuthUrl", "anon");
filterChainDefinitionMap.put("/auth/apply", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/auth/logout", "logout");
filterChainDefinitionMap.put("/**", "jwt");
return filterChainDefinitionMap;
}
/**
* Shiro生命周期处理器
*/
@ -101,20 +101,8 @@ public class ShiroConfig {
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
AopAllianceAnnotationsAuthorizingMethodInterceptor advice = (AopAllianceAnnotationsAuthorizingMethodInterceptor)authorizationAttributeSourceAdvisor.getAdvice();
//重置拦截器添加新的PermissionProxyAnnotationMethodInterceptor
List<AuthorizingAnnotationMethodInterceptor> interceptors =new ArrayList<>(6);
AnnotationResolver resolver = new SpringAnnotationResolver();
interceptors.add(new PermissionProxyAnnotationMethodInterceptor(resolver));
interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
interceptors.add(new UserAnnotationMethodInterceptor(resolver));
interceptors.add(new GuestAnnotationMethodInterceptor(resolver));
advice.setMethodInterceptors(interceptors);
public CustomAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
CustomAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new CustomAuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}

View File

@ -7,8 +7,8 @@ import com.diboot.core.util.BeanUtils;
import com.diboot.core.vo.JsonResult;
import com.diboot.core.vo.Pagination;
import com.diboot.core.vo.Status;
import com.diboot.shiro.bind.annotation.PermissionsMenu;
import com.diboot.shiro.bind.annotation.RequiresPermissionsProxy;
import com.diboot.shiro.bind.annotation.PermissionsPrefix;
import com.diboot.shiro.bind.annotation.RequiresPermissionsWrapper;
import com.diboot.shiro.entity.Permission;
import com.diboot.shiro.service.PermissionService;
import com.diboot.shiro.vo.PermissionVO;
@ -29,8 +29,8 @@ import java.util.List;
* @version 2018/12/23
* Copyright © www.dibo.ltd
*/
@PermissionsMenu(menuName = "权限", menuCode = "permission")
@RestController
@PermissionsPrefix(prefix = "permission", code = "permission", name = "权限")
@RequestMapping("/permission")
public class PermissionController extends BaseCrudRestController {
@ -39,6 +39,20 @@ public class PermissionController extends BaseCrudRestController {
@Autowired
private PermissionService permissionService;
/***
* 查询Entity
* @param id ID
* @return
* @throws Exception
*/
@GetMapping("/{id}")
@RequiresPermissionsWrapper(prefix = "permissionSelf", value = {"get"}, name = "查看")
public JsonResult getModel(@PathVariable("id")Long id, HttpServletRequest request, ModelMap modelMap)
throws Exception{
PermissionVO vo = permissionService.getViewObject(id, PermissionVO.class);
return new JsonResult(vo);
}
/***
* 查询ViewObject的分页数据 (此为非继承的自定义使用案例更简化的调用父类案例请参考UserController)
* <p>
@ -48,8 +62,7 @@ public class PermissionController extends BaseCrudRestController {
* @throws Exception
*/
@GetMapping("/list")
@RequiresPermissionsProxy(value = {"permission:list"}, menuCode = "permission",
menuName = "权限", permissionName = "列表")
@RequiresPermissionsWrapper(value = {"list"}, name = "列表")
public JsonResult getVOList(HttpServletRequest request) throws Exception{
QueryWrapper<Permission> queryWrapper = buildQuery(request);
// 构建分页
@ -77,20 +90,6 @@ public class PermissionController extends BaseCrudRestController {
return super.createEntity(entity, result, modelMap);
}
/***
* 查询Entity
* @param id ID
* @return
* @throws Exception
*/
@GetMapping("/{id}")
@RequiresPermissionsProxy(value = {"permission:get"}, menuCode = "permission", menuName = "权限", permissionName = "查看")
public JsonResult getModel(@PathVariable("id")Long id, HttpServletRequest request, ModelMap modelMap)
throws Exception{
PermissionVO vo = permissionService.getViewObject(id, PermissionVO.class);
return new JsonResult(vo);
}
/***
* 更新Entity
* @param id ID

View File

@ -56,6 +56,8 @@ public class BaseJwtAuthenticationFilter extends BasicHttpAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
logger.debug("Token认证 onAccessDenied");
HttpServletRequest httpRequest = (HttpServletRequest) request;
JsonResult jsonResult = new JsonResult(Status.FAIL_INVALID_TOKEN);
this.responseJson((HttpServletResponse) response, jsonResult);
return false;

View File

@ -11,5 +11,6 @@ rootProject.name = 'diboot-v2'
include 'diboot-core'
include 'diboot-example'
include 'diboot-shiro'
include 'diboot-shiro-wx-mp'
include 'diboot-docs'