开源项目学习-jeesite1.2.7-角色管理
可以学习+利用的点
- 参数校验(通过Hibernate Validator框架校验参数,上线项目必备)
- 根据异常返回页面(发生异常之后直接返回错误页面,无需再自己捕获后跳转页面)
- 防XSS攻击(上线项目必备)
- 分离基础crud操作到,CrudDao,CrudService(充分利用继承和泛型,可以少写代码)
- 分页类Page<T>(功能强大,既可以封装数据,又可以当作查询的条件)
- 密码生成&校验(数据库中不存在明文)
- 实体类通过多重继承获取某些特性,普通数据,具有树状结构的数据
- 通过全局类获取全局参数,使用全局方法
结构分析
角色管理相关业务逻辑在RoleController中实现,该类继承了BaseController抽象类,然后注入了SystemService及OfficeService。
@Controller
@RequestMapping(value = "${adminPath}/sys/role")
public class RoleController extends BaseController {
@Autowired
private SystemService systemService;
@Autowired
private OfficeService officeService;
//..
}
BaseController
参数定义
首先实例化了日志对象以及定义了一些参数,参数的值通过@Value注解在配置文件中读取:
查看代码
/**
* 日志对象
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 管理基础路径
*/
@Value("${adminPath}")
protected String adminPath;
/**
* 前端基础路径
*/
@Value("${frontPath}")
protected String frontPath;
/**
* 前端URL后缀
*/
@Value("${urlSuffix}")
protected String urlSuffix;
验证bean
注入验证Bean所用的对象:
/**
* 验证Bean实例对象
*/
@Autowired
protected Validator validator;
这个怎么用?
- 【Spring】使用Spring的Validation完成数据后端校验
- Spring数据校验
- Hibernate-Validator的学习
- Hibernate-Validator的学习
- 【归纳总结】SpringMVC之Hibernate Validator
- Hibernate Validator详解
配置于spring-context.xml:
<!-- 配置 JSR303 Bean Validator 定义 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
定义参数校验方法
回到BaseController,接下里是检验函数,有3个,过程都是使用BeanValidators进行检验,如果抛出异常,这将异常信息转换为String List写入到Model中:
查看代码
/**
* 服务端参数有效性验证
* @param object 验证的实体对象
* @param groups 验证组
* @return 验证成功:返回true;严重失败:将错误信息添加到 message 中
*/
protected boolean beanValidator(Model model, Object object, Class<?>... groups) {
try{
BeanValidators.validateWithException(validator, object, groups);
}catch(ConstraintViolationException ex){
List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
list.add(0, "数据验证失败:");
addMessage(model, list.toArray(new String[]{}));
return false;
}
return true;
}
/**
* 服务端参数有效性验证
* @param object 验证的实体对象
* @param groups 验证组
* @return 验证成功:返回true;严重失败:将错误信息添加到 flash message 中
*/
protected boolean beanValidator(RedirectAttributes redirectAttributes, Object object, Class<?>... groups) {
try{
BeanValidators.validateWithException(validator, object, groups);
}catch(ConstraintViolationException ex){
List<String> list = BeanValidators.extractPropertyAndMessageAsList(ex, ": ");
list.add(0, "数据验证失败:");
addMessage(redirectAttributes, list.toArray(new String[]{}));
return false;
}
return true;
}
/**
* 服务端参数有效性验证
* @param object 验证的实体对象
* @param groups 验证组,不传入此参数时,同@Valid注解验证
* @return 验证成功:继续执行;验证失败:抛出异常跳转400页面。
*/
protected void beanValidator(Object object, Class<?>... groups) {
BeanValidators.validateWithException(validator, object, groups);
}
调用BeanValidators
如果检验失败,抛出异常:
/**
* 调用JSR303的validate方法, 验证失败时抛出ConstraintViolationException.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static void validateWithException(Validator validator, Object object, Class<?>... groups)
throws ConstraintViolationException {
Set constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
throw new ConstraintViolationException(constraintViolations);
}
}
接着是异常类Set转String List,有3种方法:
查看代码
/**
* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath message>.
*/
public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), " ");
}
/**
* 辅助方法, 转换Set<ConstraintViolations>为List<propertyPath message>.
*/
@SuppressWarnings("rawtypes")
public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations) {
return extractPropertyAndMessageAsList(constraintViolations, " ");
}
/**
* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath +separator+ message>.
*/
public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), separator);
}
最后是转换过程:
/**
* 辅助方法, 转换Set<ConstraintViolation>为List<propertyPath +separator+ message>.
*/
@SuppressWarnings("rawtypes")
public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations,
String separator) {
List<String> errorMessages = Lists.newArrayList();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getPropertyPath() + separator + violation.getMessage());
}
return errorMessages;
}
验证信息写入Model
回到BaseController,在获取了异常String List之后,调用addMessage方法写会Model:
查看代码
/**
* 添加Model消息
* @param message
*/
protected void addMessage(Model model, String... messages) {
StringBuilder sb = new StringBuilder();
for (String message : messages){
sb.append(message).append(messages.length>1?"<br/>":"");
}
model.addAttribute("message", sb.toString());
}
/**
* 添加Flash消息
* @param message
*/
protected void addMessage(RedirectAttributes redirectAttributes, String... messages) {
StringBuilder sb = new StringBuilder();
for (String message : messages){
sb.append(message).append(messages.length>1?"<br/>":"");
}
redirectAttributes.addFlashAttribute("message", sb.toString());
}
返回信息转换
接下来是返回信息转换函数,返回信息格式统一处理:
查看代码
/**
* 客户端返回JSON字符串
* @param response
* @param object
* @return
*/
protected String renderString(HttpServletResponse response, Object object) {
return renderString(response, JsonMapper.toJsonString(object), "application/json");
}
/**
* 客户端返回字符串
* @param response
* @param string
* @return
*/
protected String renderString(HttpServletResponse response, String string, String type) {
try {
response.reset();
response.setContentType(type);
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
return null;
} catch (IOException e) {
return null;
}
}
根据异常返回页面
ExceptionHandler怎么用呢?
/**
* 参数绑定异常
*/
@ExceptionHandler({BindException.class, ConstraintViolationException.class, ValidationException.class})
public String bindException() {
return "error/400";
}
/**
* 授权登录异常
*/
@ExceptionHandler({AuthenticationException.class})
public String authenticationException() {
return "error/403";
}
初始化前端数据,防止XSS攻击
这是些啥?
查看代码
/**
* 初始化数据绑定
* 1. 将所有传递进来的String进行HTML编码,防止XSS攻击
* 2. 将字段中Date类型转换为String类型
*/
@InitBinder
protected void initBinder(WebDataBinder binder) {
// String类型转换,将所有传递进来的String进行HTML编码,防止XSS攻击
binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
}
@Override
public String getAsText() {
Object value = getValue();
return value != null ? value.toString() : "";
}
});
// Date 类型转换
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) {
setValue(DateUtils.parseDate(text));
}
// @Override
// public String getAsText() {
// Object value = getValue();
// return value != null ? DateUtils.formatDateTime((Date)value) : "";
// }
});
}
SystemService
可以看到该类继承了BaseService,实现了InitializingBean接口,通过@Transactional(readOnly = true)表示该类存在事务管理,以及定义了3个常量及其它类的注入:
/**
* 系统管理,安全相关实体的管理类,包括用户、角色、菜单.
* @author ThinkGem
* @version 2013-12-05
*/
@Service
@Transactional(readOnly = true)
public class SystemService extends BaseService implements InitializingBean{
public static final String HASH_ALGORITHM = "SHA-1";
public static final int HASH_INTERATIONS = 1024;
public static final int SALT_SIZE = 8;
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Autowired
private MenuDao menuDao;
@Autowired
private SessionDAO sessionDao;
@Autowired
private SystemAuthorizingRealm systemRealm;
public SessionDAO getSessionDao() {
return sessionDao;
}
@Autowired
private IdentityService identityService;
//...
}
继承BaseService(SystemService)
@Transactional(SystemService)
InitializingBean接口(SystemService)
当一个类实现这个接口之后,Spring启动后,初始化Bean时,若该Bean实现InitialzingBean接口,会自动调用afterPropertiesSet()方法,完成一些用户自定义的初始化操作,该内容与工作流有关,暂略。
查看代码
/**
* 是需要同步Activiti数据,如果从未同步过,则同步数据。
*/
private static boolean isSynActivitiIndetity = true;
public void afterPropertiesSet() throws Exception {
if (!Global.isSynActivitiIndetity()){
return;
}
if (isSynActivitiIndetity){
isSynActivitiIndetity = false;
// 同步角色数据
List<Group> groupList = identityService.createGroupQuery().list();
if (groupList.size() == 0){
Iterator<Role> roles = roleDao.findAllList(new Role()).iterator();
while(roles.hasNext()) {
Role role = roles.next();
saveActivitiGroup(role);
}
}
// 同步用户数据
List<org.activiti.engine.identity.User> userList = identityService.createUserQuery().list();
if (userList.size() == 0){
Iterator<User> users = userDao.findAllList(new User()).iterator();
while(users.hasNext()) {
saveActivitiUser(users.next());
}
}
}
}
注入XXDao(SystemService)
接下来是UersDao,RoleDao,MenuDao,对应表的操作,这3个Dao都继承了CrudDao<T>
@MyBatisDao
public interface UserDao extends CrudDao<User>
@MyBatisDao
public interface RoleDao extends CrudDao<Role>
@MyBatisDao
public interface MenuDao extends CrudDao<Menu>
注入SessionDao(SystemService)
接下来是注入了SessionDao
@Autowired
private SessionDAO sessionDao;
这个接口继承了org.apache.shiro.session.mgt.eis.SessionDAO,并新增了两个获取会话的方法,org.apache.shiro.session.mgt.eis.SessionDAO是Shiro提供的用于会话CRUD的类。
public interface SessionDAO extends org.apache.shiro.session.mgt.eis.SessionDAO {
/**
* 获取活动会话
* @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话)
* @return
*/
public Collection<Session> getActiveSessions(boolean includeLeave);
/**
* 获取活动会话
* @param includeLeave 是否包括离线(最后访问时间大于3分钟为离线会话)
* @param principal 根据登录者对象获取活动会话
* @param filterSession 不为空,则过滤掉(不包含)这个会话。
* @return
*/
public Collection<Session> getActiveSessions(boolean includeLeave, Object principal, Session filterSession);
}
为什么要用?
注意我们在传统的Web应用里面在Sevice里面是没法访问HttpSession的,也不建议这样做,在Handler层的API我们在Service层应该是不能访问的,但是现在有了Shiro这个Session之后我们就可以用了。
怎么用?
该接口有两个实现类:
具体分析待补
注入认证授权类(SystemService)
@Autowired
private SystemAuthorizingRealm systemRealm;
该类重写了shiroAuthorizingRealm中的一些方法,完成了认证授权等操作,认证分析,授权分析(待补)
@Service
//@DependsOn({"userDao","roleDao","menuDao"})
public class SystemAuthorizingRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(getClass());
private SystemService systemService;
//...
}
注入IdentityService类(SystemService)
与工作流有关,暂略
@Autowired
private IdentityService identityService;
用户CRUD业务(SystemService)
接下来实现了与用户有关的CRUD方法
User类
继承关系:User ---> DataEntity<T> ---> BaseEntity<T>
DataEntity<T>中定义了一些与数据操作相关的字段,如创建者,创建日期等。
private static final long serialVersionUID = 1L;
protected String remarks; // 备注
protected User createBy; // 创建者
protected Date createDate; // 创建日期
protected User updateBy; // 更新者
protected Date updateDate; // 更新日期
protected String delFlag; // 删除标记(0:正常;1:删除;2:审核)
BaseEntity<T>中定义了一些实体类的共同字段。
private static final long serialVersionUID = 1L;
/**
* 实体编号(唯一标识)
*/
protected String id;
/**
* 当前用户
*/
protected User currentUser;
/**
* 当前实体分页对象
*/
protected Page<T> page;
/**
* 自定义SQL(SQL标识,SQL内容)
*/
protected Map<String, String> sqlMap;
/**
* 是否是新记录(默认:false),调用setIsNewRecord()设置新记录,使用自定义ID。
* 设置为true后强制执行插入语句,ID不会自动生成,需从手动传入。
*/
protected boolean isNewRecord = false;
一个查找用户的方法
public Page<User> findUser(Page<User> page, User user) {
// 生成数据权限过滤条件(dsf为dataScopeFilter的简写,在xml中使用 ${sqlMap.dsf}调用权限SQL)
user.getSqlMap().put("dsf", dataScopeFilter(user.getCurrentUser(), "o", "a"));
// 设置分页参数
user.setPage(page);
// 执行分页查询
page.setList(userDao.findList(user));
return page;
}
该方法首先调用了BaseService中的dataScopeFilter方法将生成的权限sql语句存入sqlMap,然后设置了分页,传入的是Page<User>对象。
生成密码&验证密码
这个功能仅用于修改自己的密码时的旧密码校验及修改别人密码时进行加密。其中验证密码与认证的过程相同。
生成密码:生成SALT_SIZE(8)位盐,据此对传入的明文密码进行sha-1加密(1024次),然后对密文与明文拼接并返回。
验证密码:生成密码的逆操作,先获包含在密码(存储在数据库中)中的盐,然后据此加密前端密码,最后检查加密后的密码是否与数据库中的密码相同。
/**
* 生成安全的密码,生成随机的16位salt并经过1024次 sha-1 hash
*/
public static String entryptPassword(String plainPassword) {
String plain = Encodes.unescapeHtml(plainPassword);
byte[] salt = Digests.generateSalt(SALT_SIZE);
byte[] hashPassword = Digests.sha1(plain.getBytes(), salt, HASH_INTERATIONS);
return Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword);
}
/**
* 验证密码
* @param plainPassword 明文密码
* @param password 密文密码
* @return 验证成功返回true
*/
public static boolean validatePassword(String plainPassword, String password) {
String plain = Encodes.unescapeHtml(plainPassword);
byte[] salt = Encodes.decodeHex(password.substring(0,16));
byte[] hashPassword = Digests.sha1(plain.getBytes(), salt, HASH_INTERATIONS);
return password.equals(Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword));
}
角色CRUD业务(SystemService)
Role类
继承关系:Role ---> DataEntity<T> ---> BaseEntity<T>
保存角色
可以看到该方法同时更新了别的信息
查看代码
@Transactional(readOnly = false)
public void saveRole(Role role) {
if (StringUtils.isBlank(role.getId())){
role.preInsert();
roleDao.insert(role);
// 同步到Activiti
saveActivitiGroup(role);
}else{
role.preUpdate();
roleDao.update(role);
}
// 更新角色与菜单关联
roleDao.deleteRoleMenu(role);
if (role.getMenuList().size() > 0){
roleDao.insertRoleMenu(role);
}
// 更新角色与部门关联
roleDao.deleteRoleOffice(role);
if (role.getOfficeList().size() > 0){
roleDao.insertRoleOffice(role);
}
// 同步到Activiti
saveActivitiGroup(role);
// 清除用户角色缓存
UserUtils.removeCache(UserUtils.CACHE_ROLE_LIST);
// // 清除权限缓存
// systemRealm.clearAllCachedAuthorizationInfo();
}
别的没啥感觉,暂略
菜单CRUD业务(SystemService)
Menu类
继承关系:Menu ---> DataEntity<T> ---> BaseEntity<T>
别的没啥感觉,暂略
CrudDao<T>&BaseDao
BaseDao
CrudDao<T>继承了BaseDao,但里面啥也没有:
/**
* DAO支持类实现
* @author ThinkGem
* @version 2014-05-16
*/
public interface BaseDao {
}
CrudDao<T>
定义了与T相关的基础CRUD操作,输入参数为id或T:
查看代码
/**
* DAO支持类实现
* @author ThinkGem
* @version 2014-05-16
* @param <T>
*/
public interface CrudDao<T> extends BaseDao {
/**
* 获取单条数据
* @param id
* @return
*/
public T get(String id);
/**
* 获取单条数据
* @param entity
* @return
*/
public T get(T entity);
/**
* 查询数据列表,如果需要分页,请设置分页对象,如:entity.setPage(new Page<T>());
* @param entity
* @return
*/
public List<T> findList(T entity);
/**
* 查询所有数据列表
* @param entity
* @return
*/
public List<T> findAllList(T entity);
/**
* 查询所有数据列表
* @see public List<T> findAllList(T entity)
* @return
*/
@Deprecated
public List<T> findAllList();
/**
* 插入数据
* @param entity
* @return
*/
public int insert(T entity);
/**
* 更新数据
* @param entity
* @return
*/
public int update(T entity);
/**
* 删除数据(一般为逻辑删除,更新del_flag字段为1)
* @param id
* @see public int delete(T entity)
* @return
*/
@Deprecated
public int delete(String id);
/**
* 删除数据(一般为逻辑删除,更新del_flag字段为1)
* @param entity
* @return
*/
public int delete(T entity);
}
注意到某些字段或方法标注了@Deprecated注解,这是啥?
@Deprecated是java内置注解,此注解可以用在方法,属性,类上,表示不推荐程序员使用,但是还可以使用,可以看到被标注的方法划掉了:
分页类(Page<T>)
该项目用的是jsp,所以在这个类中涉及到了Model层的写入。这个类实现的功能有:
- 利用构造函数限制分页效果,即分页/不分页,每页多少数据等。
- 如果不使用分页,则返回T类型的数据。
查看代码
//xxService
Page<User> page = systemService.findUser(new Page<User>(1, -1), user);
for (User e : page.getList()) {
Map<String, Object> map = Maps.newHashMap();
map.put("id", e.getId());
map.put("pId", 0);
map.put("name", e.getName());
mapList.add(map);
}
//Page<T>
private List<T> list = new ArrayList<T>();
/**
* 获取本页数据对象列表
* @return List<T>
*/
public List<T> getList() {
return list;
}
分页信息存储在cookie中,由一个构造函数进行读取。
查看代码
//new一个分页类
Page<Act> page = new Page<Act>(request, response);
/**
* 构造方法
* @param request 传递 repage 参数,来记住页码
* @param response 用于设置 Cookie,记住页码
* @param defaultPageSize 默认分页大小,如果传递 -1 则为不分页,返回所有数据
*/
public Page(HttpServletRequest request, HttpServletResponse response, int defaultPageSize){
// 设置页码参数(传递repage参数,来记住页码)
String no = request.getParameter("pageNo");
if (StringUtils.isNumeric(no)){
CookieUtils.setCookie(response, "pageNo", no);
this.setPageNo(Integer.parseInt(no));
}else if (request.getParameter("repage")!=null){
no = CookieUtils.getCookie(request, "pageNo");
if (StringUtils.isNumeric(no)){
this.setPageNo(Integer.parseInt(no));
}
}
// 设置页面大小参数(传递repage参数,来记住页码大小)
String size = request.getParameter("pageSize");
if (StringUtils.isNumeric(size)){
CookieUtils.setCookie(response, "pageSize", size);
this.setPageSize(Integer.parseInt(size));
}else if (request.getParameter("repage")!=null){
size = CookieUtils.getCookie(request, "pageSize");
if (StringUtils.isNumeric(size)){
this.setPageSize(Integer.parseInt(size));
}
}else if (defaultPageSize != -2){
this.pageSize = defaultPageSize;
}
// 设置页面分页函数
String funcName = request.getParameter("funcName");
if (StringUtils.isNotBlank(funcName)){
CookieUtils.setCookie(response, "funcName", funcName);
this.setFuncName(funcName);
}else if (request.getParameter("repage")!=null){
funcName = CookieUtils.getCookie(request, "funcName");
if (StringUtils.isNotBlank(funcName)){
this.setFuncName(funcName);
}
}
// 设置排序参数
String orderBy = request.getParameter("orderBy");
if (StringUtils.isNotBlank(orderBy)){
this.setOrderBy(orderBy);
}
}
- 请求中分页信息的校验,即如果请求要查第10页内容,但没有这么多页,该类会进行修正。
查看代码
/**
* 设置本页数据对象列表
* @param list
*/
public Page<T> setList(List<T> list) {
this.list = list;
initialize();
return this;
}
/**
* 初始化参数
*/
public void initialize(){
//1
this.first = 1;
this.last = (int)(count / (this.pageSize < 1 ? 20 : this.pageSize) + first - 1);
if (this.count % this.pageSize != 0 || this.last == 0) {
this.last++;
}
if (this.last < this.first) {
this.last = this.first;
}
if (this.pageNo <= 1) {
this.pageNo = this.first;
this.firstPage=true;
}
if (this.pageNo >= this.last) {
this.pageNo = this.last;
this.lastPage=true;
}
if (this.pageNo < this.last - 1) {
this.next = this.pageNo + 1;
} else {
this.next = this.last;
}
if (this.pageNo > 1) {
this.prev = this.pageNo - 1;
} else {
this.prev = this.first;
}
//2
if (this.pageNo < this.first) {// 如果当前页小于首页
this.pageNo = this.first;
}
if (this.pageNo > this.last) {// 如果当前页大于尾页
this.pageNo = this.last;
}
}
该类即可以用于数据封装,又可以作为查询条件类:
查看代码
//按分页条件查询及封装数据
public Page<User> findUser(Page<User> page, User user) {
// 生成数据权限过滤条件(dsf为dataScopeFilter的简写,在xml中使用 ${sqlMap.dsf}调用权限SQL)
user.getSqlMap().put("dsf", dataScopeFilter(user.getCurrentUser(), "o", "a"));
// 设置分页参数
user.setPage(page);
// 执行分页查询
page.setList(userDao.findList(user));
return page;
}
BaseService
BaseService中有两个方法,作用是根据用户所在的部门和角色信息生成一部分sql语句,这条sql语句拼接在where之后,用于权限限制,即只返回对应用户能够访问,修改的数据,比如A公司是C公司的子公司,A的某个部门的部长可以访问本部的所有信息,但是不能访问其它部门的;A的最高管理员可以访问A的所有信息,但是不能访问C的;C的最高管理员可以访问A,C 的所有信息。
查看代码
/**
* 数据范围过滤
* @param user 当前用户对象,通过“entity.getCurrentUser()”获取
* @param officeAlias 机构表别名,多个用“,”逗号隔开。
* @param userAlias 用户表别名,多个用“,”逗号隔开,传递空,忽略此参数
* @return 标准连接条件对象
*/
public static String dataScopeFilter(User user, String officeAlias, String userAlias) {
StringBuilder sqlString = new StringBuilder();
// 进行权限过滤,多个角色权限范围之间为或者关系。
List<String> dataScope = Lists.newArrayList();
// 超级管理员,跳过权限过滤
if (!user.isAdmin()){
boolean isDataScopeAll = false;
for (Role r : user.getRoleList()){
for (String oa : StringUtils.split(officeAlias, ",")){
if (!dataScope.contains(r.getDataScope()) && StringUtils.isNotBlank(oa)){
if (Role.DATA_SCOPE_ALL.equals(r.getDataScope())){
isDataScopeAll = true;
}
else if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(r.getDataScope())){
sqlString.append(" OR " + oa + ".id = '" + user.getCompany().getId() + "'");
sqlString.append(" OR " + oa + ".parent_ids LIKE '" + user.getCompany().getParentIds() + user.getCompany().getId() + ",%'");
}
else if (Role.DATA_SCOPE_COMPANY.equals(r.getDataScope())){
sqlString.append(" OR " + oa + ".id = '" + user.getCompany().getId() + "'");
// 包括本公司下的部门 (type=1:公司;type=2:部门)
sqlString.append(" OR (" + oa + ".parent_id = '" + user.getCompany().getId() + "' AND " + oa + ".type = '2')");
}
else if (Role.DATA_SCOPE_OFFICE_AND_CHILD.equals(r.getDataScope())){
sqlString.append(" OR " + oa + ".id = '" + user.getOffice().getId() + "'");
sqlString.append(" OR " + oa + ".parent_ids LIKE '" + user.getOffice().getParentIds() + user.getOffice().getId() + ",%'");
}
else if (Role.DATA_SCOPE_OFFICE.equals(r.getDataScope())){
sqlString.append(" OR " + oa + ".id = '" + user.getOffice().getId() + "'");
}
else if (Role.DATA_SCOPE_CUSTOM.equals(r.getDataScope())){
// String officeIds = StringUtils.join(r.getOfficeIdList(), "','");
// if (StringUtils.isNotEmpty(officeIds)){
// sqlString.append(" OR " + oa + ".id IN ('" + officeIds + "')");
// }
sqlString.append(" OR EXISTS (SELECT 1 FROM sys_role_office WHERE role_id = '" + r.getId() + "'");
sqlString.append(" AND office_id = " + oa +".id)");
}
//else if (Role.DATA_SCOPE_SELF.equals(r.getDataScope())){
dataScope.add(r.getDataScope());
}
}
}
// 如果没有全部数据权限,并设置了用户别名,则当前权限为本人;如果未设置别名,当前无权限为已植入权限
if (!isDataScopeAll){
if (StringUtils.isNotBlank(userAlias)){
for (String ua : StringUtils.split(userAlias, ",")){
sqlString.append(" OR " + ua + ".id = '" + user.getId() + "'");
}
}else {
for (String oa : StringUtils.split(officeAlias, ",")){
//sqlString.append(" OR " + oa + ".id = " + user.getOffice().getId());
sqlString.append(" OR " + oa + ".id IS NULL");
}
}
}else{
// 如果包含全部权限,则去掉之前添加的所有条件,并跳出循环。
sqlString = new StringBuilder();
}
}
if (StringUtils.isNotBlank(sqlString.toString())){
return " AND (" + sqlString.substring(4) + ")";
}
return "";
}
/**
* 数据范围过滤(符合业务表字段不同的时候使用,采用exists方法)
* @param entity 当前过滤的实体类
* @param sqlMapKey sqlMap的键值,例如设置“dsf”时,调用方法:${sqlMap.sdf}
* @param officeWheres office表条件,组成:部门表字段=业务表的部门字段
* @param userWheres user表条件,组成:用户表字段=业务表的用户字段
* @example
* dataScopeFilter(user, "dsf", "id=a.office_id", "id=a.create_by");
* dataScopeFilter(entity, "dsf", "code=a.jgdm", "no=a.cjr"); // 适应于业务表关联不同字段时使用,如果关联的不是机构id是code。
*/
public static void dataScopeFilter(BaseEntity<?> entity, String sqlMapKey, String officeWheres, String userWheres) {
User user = entity.getCurrentUser();
// 如果是超级管理员,则不过滤数据
if (user.isAdmin()) {
return;
}
// 数据范围(1:所有数据;2:所在公司及以下数据;3:所在公司数据;4:所在部门及以下数据;5:所在部门数据;8:仅本人数据;9:按明细设置)
StringBuilder sqlString = new StringBuilder();
// 获取到最大的数据权限范围
String roleId = "";
int dataScopeInteger = 8;
for (Role r : user.getRoleList()){
int ds = Integer.valueOf(r.getDataScope());
if (ds == 9){
roleId = r.getId();
dataScopeInteger = ds;
break;
}else if (ds < dataScopeInteger){
roleId = r.getId();
dataScopeInteger = ds;
}
}
String dataScopeString = String.valueOf(dataScopeInteger);
// 生成部门权限SQL语句
for (String where : StringUtils.split(officeWheres, ",")){
if (Role.DATA_SCOPE_COMPANY_AND_CHILD.equals(dataScopeString)){
// 包括本公司下的部门 (type=1:公司;type=2:部门)
sqlString.append(" AND EXISTS (SELECT 1 FROM SYS_OFFICE");
sqlString.append(" WHERE type='2'");
sqlString.append(" AND (id = '" + user.getCompany().getId() + "'");
sqlString.append(" OR parent_ids LIKE '" + user.getCompany().getParentIds() + user.getCompany().getId() + ",%')");
sqlString.append(" AND " + where +")");
}
else if (Role.DATA_SCOPE_COMPANY.equals(dataScopeString)){
sqlString.append(" AND EXISTS (SELECT 1 FROM SYS_OFFICE");
sqlString.append(" WHERE type='2'");
sqlString.append(" AND id = '" + user.getCompany().getId() + "'");
sqlString.append(" AND " + where +")");
}
else if (Role.DATA_SCOPE_OFFICE_AND_CHILD.equals(dataScopeString)){
sqlString.append(" AND EXISTS (SELECT 1 FROM SYS_OFFICE");
sqlString.append(" WHERE (id = '" + user.getOffice().getId() + "'");
sqlString.append(" OR parent_ids LIKE '" + user.getOffice().getParentIds() + user.getOffice().getId() + ",%')");
sqlString.append(" AND " + where +")");
}
else if (Role.DATA_SCOPE_OFFICE.equals(dataScopeString)){
sqlString.append(" AND EXISTS (SELECT 1 FROM SYS_OFFICE");
sqlString.append(" WHERE id = '" + user.getOffice().getId() + "'");
sqlString.append(" AND " + where +")");
}
else if (Role.DATA_SCOPE_CUSTOM.equals(dataScopeString)){
sqlString.append(" AND EXISTS (SELECT 1 FROM sys_role_office ro123456, sys_office o123456");
sqlString.append(" WHERE ro123456.office_id = o123456.id");
sqlString.append(" AND ro123456.role_id = '" + roleId + "'");
sqlString.append(" AND o123456." + where +")");
}
}
// 生成个人权限SQL语句
for (String where : StringUtils.split(userWheres, ",")){
if (Role.DATA_SCOPE_SELF.equals(dataScopeString)){
sqlString.append(" AND EXISTS (SELECT 1 FROM sys_user");
sqlString.append(" WHERE id='" + user.getId() + "'");
sqlString.append(" AND " + where + ")");
}
}
// System.out.println("dataScopeFilter: " + sqlString.toString());
// 设置到自定义SQL对象
entity.getSqlMap().put(sqlMapKey, sqlString.toString());
}
OfficeService
结构
该类继承了TreeService<D,T> ,其中D是OfficeDao,T是Office;OfficeDao继承了TreeDao,Office继承了TreeEntity
/**
* 机构Service
* @author ThinkGem
* @version 2014-05-16
*/
@Service
@Transactional(readOnly = true)
public class OfficeService extends TreeService<OfficeDao, Office> {
//...
}
接下来就是对Office的一些crud操作
TreeService<D,T>
结构
TreeService其中D继承了TreeDao<T>,T继承了TreeEntity<T>
TreeService继承了CrudService<D,T>。
@Transactional(readOnly = true)
public abstract class TreeService<D extends TreeDao<T>, T extends TreeEntity<T>> extends CrudService<D, T> {
TreeDao<T>
结构
继承了CrudDao,定义了一些操作树结构数据的方法
/**
* DAO支持类实现
* @author ThinkGem
* @version 2014-05-16
* @param <T>
*/
public interface TreeDao<T extends TreeEntity<T>> extends CrudDao<T> {
/**
* 找到所有子节点
* @param entity
* @return
*/
public List<T> findByParentIdsLike(T entity);
/**
* 更新所有父节点字段
* @param entity
* @return
*/
public int updateParentIds(T entity);
}
TreeEntity<T>
结构
继承自DataEntity,在其中定义了节点信息,其它待补
/**
* 数据Entity类
* @author ThinkGem
* @version 2014-05-16
*/
public abstract class TreeEntity<T> extends DataEntity<T> {
private static final long serialVersionUID = 1L;
protected T parent; // 父级编号
protected String parentIds; // 所有父级编号
protected String name; // 机构名称
protected Integer sort; // 排序
public TreeEntity() {
super();
this.sort = 30;
}
public TreeEntity(String id) {
super(id);
}
}
其它方法
/**
* 父对象,只能通过子类实现,父类实现mybatis无法读取
* @return
*/
@JsonBackReference
@NotNull
public abstract T getParent();
/**
* 父对象,只能通过子类实现,父类实现mybatis无法读取
* @return
*/
public abstract void setParent(T parent);
@Length(min=1, max=2000)
public String getParentIds() {
return parentIds;
}
public void setParentIds(String parentIds) {
this.parentIds = parentIds;
}
@Length(min=1, max=100)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public String getParentId() {
String id = null;
if (parent != null){
id = (String)Reflections.getFieldValue(parent, "id");
}
return StringUtils.isNotBlank(id) ? id : "0";
}
CrudService<D,T>
CrudService<D,T>继承了BaseService,其中D继承了CrudDao<T>,T继承了DataEntity<T>:
/**
* Service基类
* @author ThinkGem
* @version 2014-05-16
*/
@Transactional(readOnly = true)
public abstract class CrudService<D extends CrudDao<T>, T extends DataEntity<T>> extends BaseService {
//...
}
接着就是对通过D对T的一些常规操作
查看代码
/**
* 获取单条数据
* @param id
* @return
*/
public T get(String id) {
return dao.get(id);
}
/**
* 获取单条数据
* @param entity
* @return
*/
public T get(T entity) {
return dao.get(entity);
}
/**
* 查询列表数据
* @param entity
* @return
*/
public List<T> findList(T entity) {
return dao.findList(entity);
}
/**
* 查询分页数据
* @param page 分页对象
* @param entity
* @return
*/
public Page<T> findPage(Page<T> page, T entity) {
entity.setPage(page);
page.setList(dao.findList(entity));
return page;
}
/**
* 保存数据(插入或更新)
* @param entity
*/
@Transactional(readOnly = false)
public void save(T entity) {
if (entity.getIsNewRecord()){
entity.preInsert();
dao.insert(entity);
}else{
entity.preUpdate();
dao.update(entity);
}
}
/**
* 删除数据
* @param entity
*/
@Transactional(readOnly = false)
public void delete(T entity) {
dao.delete(entity);
}
RoleController
注入需要的对象之后就是实现具体业务了
例如保存角色操作,在进行保存之前,以此进行了:判断是否由超级管理员修改-判断是否是演示模式-判断参数是否正确-判断参数是否正确-判断中文名、英文名是否重复-保存。
前两个都使用了全局参数类Global,第三个使用的是BaseController中的beanValidator方法
@RequiresPermissions("sys:role:edit")
@RequestMapping(value = "save")
public String save(Role role, Model model, RedirectAttributes redirectAttributes) {
if(!UserUtils.getUser().isAdmin()&&role.getSysData().equals(Gl obal.YES)){
addMessage(redirectAttributes, "越权操作,只有超级管理员才能修改此数据!");
return "redirect:" + adminPath + "/sys/role/?repage";
}
if(Global.isDemoMode()){
addMessage(redirectAttributes, "演示模式,不允许操作!");
return "redirect:" + adminPath + "/sys/role/?repage";
}
if (!beanValidator(model, role)){
return form(role, model);
}
if (!"true".equals(checkName(role.getOldName(), role.getName()))){
addMessage(model, "保存角色'" + role.getName() + "'失败, 角色名已存在");
return form(role, model);
}
if (!"true".equals(checkEnname(role.getOldEnname(), role.getEnname()))){
addMessage(model, "保存角色'" + role.getName() + "'失败, 英文名已存在");
return form(role, model);
}
systemService.saveRole(role);
addMessage(redirectAttributes, "保存角色'" + role.getName() + "'成功");
return "redirect:" + adminPath + "/sys/role/?repage";
}
Global
全局配置类,懒汉式单例类.在第一次调用的时候实例化自己,里面都是全局性的参数和方法
查看代码
public class Global {
private Global() {}
/**
* 当前对象实例
*/
private static Global global = null;
/**
* 静态工厂方法 获取当前对象实例 多线程安全单例模式(使用双重同步锁)
*/
public static synchronized Global getInstance() {
if (global == null) {
synchronized (Global.class) {
if (global == null)
global = new Global();
}
}
return global;
}
/**
* 保存全局属性值
*/
private static Map<String, String> map = Maps.newHashMap();
/**
* 属性文件加载对象
*/
private static PropertiesLoader loader = new PropertiesLoader("jeesite.properties");
/**
* 显示/隐藏
*/
public static final String SHOW = "1";
public static final String HIDE = "0";
/**
* 是/否
*/
public static final String YES = "1";
public static final String NO = "0";
/**
* 对/错
*/
public static final String TRUE = "true";
public static final String FALSE = "false";
/**
* 上传文件基础虚拟路径
*/
public static final String USERFILES_BASE_URL = "/userfiles/";
/**
* 获取配置
*
* @see ${fns:getConfig('adminPath')}
*/
public static String getConfig(String key) {
String value = map.get(key);
if (value == null) {
value = loader.getProperty(key);
map.put(key, value != null ? value : StringUtils.EMPTY);
}
return value;
}
/**
* 获取管理端根路径
*/
public static String getAdminPath() {
return getConfig("adminPath");
}
/**
* 获取前端根路径
*/
public static String getFrontPath() {
return getConfig("frontPath");
}
/**
* 获取URL后缀
*/
public static String getUrlSuffix() {
return getConfig("urlSuffix");
}
/**
* 是否是演示模式,演示模式下不能修改用户、角色、密码、菜单、授权
*/
public static Boolean isDemoMode() {
String dm = getConfig("demoMode");
return "true".equals(dm) || "1".equals(dm);
}
/**
* 在修改系统用户和角色时是否同步到Activiti
*/
public static Boolean isSynActivitiIndetity() {
String dm = getConfig("activiti.isSynActivitiIndetity");
return "true".equals(dm) || "1".equals(dm);
}
/**
* 页面获取常量
*
* @see ${fns:getConst('YES')}
*/
public static Object getConst(String field) {
try {
return Global.class.getField(field).get(null);
} catch (Exception e) {
// 异常代表无配置,这里什么也不做
}
return null;
}
/**
* 获取上传文件的根目录
*
* @return
*/
public static String getUserfilesBaseDir() {
String dir = getConfig("userfiles.basedir");
if (StringUtils.isBlank(dir)) {
try {
dir = ServletContextFactory.getServletContext().getRealPath("/");
} catch (Exception e) {
return "";
}
}
if (!dir.endsWith("/")) {
dir += "/";
}
// System.out.println("userfiles.basedir: " + dir);
return dir;
}
/**
* 获取工程路径
*
* @return
*/
public static String getProjectPath() {
// 如果配置了工程路径,则直接返回,否则自动获取。
String projectPath = Global.getConfig("projectPath");
if (StringUtils.isNotBlank(projectPath)) {
return projectPath;
}
try {
File file = new DefaultResourceLoader().getResource("").getFile();
if (file != null) {
while (true) {
File f = new File(file.getPath() + File.separator + "src" + File.separator + "main");
if (f == null || f.exists()) {
break;
}
if (file.getParentFile() != null) {
file = file.getParentFile();
} else {
break;
}
}
projectPath = file.toString();
}
} catch (IOException e) {
e.printStackTrace();
}
return projectPath;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)