1. 程式人生 > >react + spring boot 選單許可權控制-動態載入二級選單

react + spring boot 選單許可權控制-動態載入二級選單

首先是給路徑建表,存在資料庫裡

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `route_config`
-- ----------------------------
DROP TABLE IF EXISTS `route_config`;
CREATE TABLE `route_config` (
  `id` int(11) NOT NULL,
  `key` varchar(255) DEFAULT NULL,
   #圖示
  `icon` varchar(255) DEFAULT NULL,
   #父路徑id
  `parent_id` int(11) DEFAULT NULL,
   #路徑
  `url` varchar(255) DEFAULT NULL,
   #路徑對應的文字
  `title` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

路徑表和許可權表的連線表

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `route_permission`
-- ----------------------------
DROP TABLE IF EXISTS `route_permission`;
CREATE TABLE `route_permission` (
  `id` int(11) NOT NULL,
   #路徑id
  `rid` int(11) DEFAULT NULL,
   #許可權id
  `pid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

許可權表

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `permission`
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID,主鍵',
  `name` varchar(64) DEFAULT NULL COMMENT '許可權名',
  `parent_id` int(10) unsigned DEFAULT NULL COMMENT '上級許可權ID',
  `abbreviation` varchar(32) DEFAULT NULL COMMENT '簡稱',
  `type` varchar(32) DEFAULT NULL COMMENT '許可權型別',
  `description` varchar(255) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8 COMMENT='許可權';

後臺用shiro框架來管理許可權,在RouteServiceImpl中實現getRoutes(),流程可以概括為 先獲取當前登入的使用者,從使用者物件中取到使用者所對應的角色, 再遍歷角色,得到對應的許可權,根據許可權查詢到對應的路徑資訊,再呼叫getTree方法生成選單樹。

@Service
public class RouteServiceImpl implements RouteService{
	@Autowired
	private CurrentUser currentUser;
	@Autowired
	RouteConfigMapper routeConfigMapper;
	@Override
	public List<Tree> getRoutes() {
		List<Tree> list = new ArrayList<Tree>();
		if(currentUser.getCurrentUser()!=null) {
			List<Integer> permission = new ArrayList<Integer>();
			List<Role> roles =  currentUser.getCurrentUser().getRoles();
			for(int i=0;i<roles.size();i++) {
				List<Permission> permissions = roles.get(i).getPermissions();
				for(int j=0;j<permissions.size();j++) {
					permission.add(permissions.get(j).getId());
				}
			}
			//符合許可權的子路徑
			Set<RouteConfig> routes = routeConfigMapper.selectRouteConfig(permission);
			List<RouteConfig> parentRoute = routeConfigMapper.selectByPid(0);
			//給陣列加入父路徑
			for(int i=0;i<parentRoute.size();i++) {
				routes.add(parentRoute.get(i));
			}
			List<RouteConfig> routeList = new ArrayList<RouteConfig>(routes);
			//按id排序
			Collections.sort(routeList);
			list = RouteConfigTree.getTree(routeList);
		}
		return list;
	}
}

RouteConfigTree類

public class RouteConfigTree {
	/**
	 * 計數
	 */
	private static int length = 0;
	/**
	 * 遞迴構造樹
	 */
	public static void buildTree(Tree parentTree, List<RouteConfig> list) {
		List<Tree> childList = new ArrayList<>();
		for(int i = 0; i < list.size(); i++) {
			if(list.get(i).getParentRouteConfig()!= null && 
					list.get(i).getParentRouteConfig().getId() == parentTree.getId()) {
				Tree tree = new Tree();
				tree.setId(list.get(i).getId());
				tree.setKey(list.get(i).getKey());
				tree.setTitle(list.get(i).getTitle());
				tree.setParentKey(parentTree.getId());
				tree.setIcon(list.get(i).getIcon());
				tree.setUrl(list.get(i).getUrl());
				childList.add(tree);
				length--;
			}
		}
		if(childList.size() > 0) {
			parentTree.setRoute(childList);
		}
		
		if(length > 0) {
			for(int i = 0; i < childList.size(); i++) {
				buildTree(childList.get(i), list);
			}
		}
	}
	/**
	 * 構造樹
	 */
	public static List<Tree> getTree(List<RouteConfig> list) {
		System.out.println("routes"+list);
		List<Tree> treeList = new ArrayList<>();
		length = list.size();
		
		//先找根
		for(int i = 0; i < list.size(); i++) {
			if(list.get(i).getParentRouteConfig()== null) {
				Tree tree = new Tree();
				tree.setId(list.get(i).getId());
				tree.setKey(list.get(i).getKey());
				tree.setTitle(list.get(i).getTitle());
				tree.setIcon(list.get(i).getIcon());
				tree.setParentKey(null);
				tree.setUrl(list.get(i).getUrl());
				treeList.add(tree);
				length--;
			}
		}
		
		//再找子
		for(int i = 0; i < treeList.size(); i++) {
			buildTree(treeList.get(i), list);
		}
		//把沒有子節點的根節點刪除
		for(int i = 0; i < treeList.size(); i++) {
			System.out.println(treeList.get(i));
			if(treeList.get(i).getRoute()==null) {
				treeList.remove(i);
			}
		}
		return treeList;
	}
}

Tree類

public class Tree implements Serializable {
	
	private static final long serialVersionUID = 1L;
	//key
	private String key;
	//當前節點id
	private Integer Id;
	//父id
	private Integer parentKey;
	//路徑文字內容
	private String title;
	private List<Tree> route;
	private String icon;
	private String url;
	
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public String getIcon() {
		return icon;
	}
	public void setIcon(String icon) {
		this.icon = icon;
	}
	public Integer getId() {
		return Id;
	}
	public void setId(Integer id) {
		Id = id;
	}
	public String getKey() {
		return key;
	}
	public void setKey(String key) {
		this.key = key;
	}
	public static long getSerialversionuid() {
		return serialVersionUID;
	}
	public Integer getParentKey() {
		return parentKey;
	}
	public void setParentKey(Integer parentKey) {
		this.parentKey = parentKey;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public List<Tree> getRoute() {
		return route;
	}
	public void setRoute(List<Tree> route) {
		this.route = route;
	}
	@Override
	public String toString() {
		return "Tree [key=" + key + ", Id=" + Id + ", parentKey=" + parentKey + ", title=" + title + ", route=" + route
				+ ", icon=" + icon + ", url=" + url + "]";
	}
}

Controller中,把選單樹轉成json形式返回前端,到這裡,後端部分就結束了

@RequestMapping("/getSliderList")
@ResponseBody
public String getSliderList() {
	List<Tree> routes = routeService.getRoutes();
	JSONObject resultJS = new JSONObject();
	resultJS.put("list", routes);
	return resultJS.toJSONString();
}

前端 react + redux 

程式碼結構如圖

首先是store下的index.js

import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
	applyMiddleware(thunk)
));

export default store;

store下的reducer.js

import { combineReducers } from 'redux';
import CommonReducer from './CommonReducer/reducer'
const reducer = combineReducers({
	common:CommonReducer
});

export default reducer;

app.js

import Leftsider from './pages/left'
class App extends Component {
  render() {
    return (
      <Provider store={store}>
      <Router history={history}>
            <Leftsider/>
            <div id="right">
                <Right></Right>
            </div>
          </div>
       </Router>
     </Provider>
    );
  }
}
export default App;

left.js 側邊欄部分,每次載入這個元件,都會從後臺請求側邊欄資料,存在store中

class Leftsider extends React.Component{
  componentDidMount(){
    this.props.initSliderList();
  }
  render(){
    return(
      <div id="left">
         <Sider
       trigger={null}
     >
       <div className="logo" />
       <Menu
       defaultSelectedKeys={['1']}
       defaultOpenKeys={['sub1']}
       mode="inline"
       theme="dark"
     >
      {       
                this.props.list.map(function(item,index){
                 return (
                     <SubMenu key={index} title={<span>{item.title}</span>}>
                     {
                          item.route.map(function(item1,number){
                              return (
                                 <Menu.Item key={item1.id} id={item1.id}><Link to={item1.url}>{item1.title}</Link></Menu.Item>
                              );
                         })
                     }
                     </SubMenu> 
                 );
               }
             )
        }
     </Menu>
     </Sider>
     </div>
     );
  }
}
const mapStateToProps = (state)=>({
  list:state.common.list
})
const mapDispatch = (dispath)=>{
  return {
    initSliderList(){
      let list=[]
      axios.get('getSliderList').then(res=>{
        list = res.data.list
        dispath({
          type:'init_slider_list',
          list:list
        })
      })
    }
}
}
export default connect(mapStateToProps,mapDispatch)(withRouter(Leftsider));

commonReducer-reducer.js


const defaultState = {
    list:[]
}

export default function CommonReducer(state = defaultState,action){
    switch(action.type){
        case 'init_slider_list':
            return initSliderList(state,action);
        case 'reset_slider_list':
            return resetSliderList(state,action);
        default:
            return state
    }
}
const initSliderList=(state,action)=>{
    return {
        ...state,...{list:action.list}
    }
}

const resetSliderList=(state,action)=>{
    return {
        ...state,...{list:[]}
    }
}

到這裡,前端部分結束,完成動態載入選單