diff --git a/opsli-api/src/main/java/org/opsli/api/base/result/ResultVo.java b/opsli-api/src/main/java/org/opsli/api/base/result/ResultVo.java index f1ad918..ad3f64c 100644 --- a/opsli-api/src/main/java/org/opsli/api/base/result/ResultVo.java +++ b/opsli-api/src/main/java/org/opsli/api/base/result/ResultVo.java @@ -139,8 +139,8 @@ public class ResultVo implements Serializable { * @return ResultVo */ @JsonIgnore//返回对象时忽略此属性 - public static ResultVo error(String msg) { - ResultVo ret = new ResultVo<>(); + public static ResultVo error(String msg) { + ResultVo ret = new ResultVo<>(); ret.setMsg(msg); ret.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value()); ret.setSuccess(false); @@ -154,8 +154,8 @@ public class ResultVo implements Serializable { * @return ResultVo */ @JsonIgnore//返回对象时忽略此属性 - public static ResultVo error(int code, String msg) { - ResultVo ret = new ResultVo<>(); + public static ResultVo error(int code, String msg) { + ResultVo ret = new ResultVo<>(); ret.setMsg(msg); ret.setCode(code); ret.setSuccess(false); diff --git a/opsli-api/src/main/java/org/opsli/api/base/warpper/ApiWrapper.java b/opsli-api/src/main/java/org/opsli/api/base/warpper/ApiWrapper.java index 5918f7b..b708dd7 100644 --- a/opsli-api/src/main/java/org/opsli/api/base/warpper/ApiWrapper.java +++ b/opsli-api/src/main/java/org/opsli/api/base/warpper/ApiWrapper.java @@ -3,6 +3,7 @@ package org.opsli.api.base.warpper; import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.*; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.Version; import com.fasterxml.jackson.annotation.JsonFormat; @@ -90,4 +91,7 @@ public abstract class ApiWrapper implements Serializable { @Version private Integer version; + /** 是否是内部Api调用 */ + @TableField(exist = false) + private Boolean izApi = false; } diff --git a/opsli-api/src/main/java/org/opsli/api/utils/ValidationUtil.java b/opsli-api/src/main/java/org/opsli/api/utils/ValidationUtil.java index e19b6e8..a065021 100644 --- a/opsli-api/src/main/java/org/opsli/api/utils/ValidationUtil.java +++ b/opsli-api/src/main/java/org/opsli/api/utils/ValidationUtil.java @@ -6,7 +6,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.opsli.api.msg.ValidationMsg; -import org.opsli.api.wrapper.system.dict.SysDictModel; +import org.opsli.api.wrapper.system.dict.DictModel; import org.opsli.common.annotation.validation.ValidationArgs; import org.opsli.common.annotation.validation.ValidationArgsMax; import org.opsli.common.annotation.validation.ValidationArgsMin; @@ -334,13 +334,13 @@ public final class ValidationUtil { } public static void main(String[] args) { - SysDictModel sysDictModel = new SysDictModel(); - sysDictModel.setTypeCode("asdsa"); - sysDictModel.setTypeName("阿哈哈哈哈"); - sysDictModel.setRemark("测试11232131231231223123"); - sysDictModel.setIzLock('1'); + DictModel dictModel = new DictModel(); + dictModel.setTypeCode("asdsa"); + dictModel.setTypeName("阿哈哈哈哈"); + dictModel.setRemark("测试11232131231231223123"); + dictModel.setIzLock('1'); - ValidationUtil.verify(sysDictModel); + ValidationUtil.verify(dictModel); } diff --git a/opsli-api/src/main/java/org/opsli/api/web/system/dict/DictApi.java b/opsli-api/src/main/java/org/opsli/api/web/system/dict/DictApi.java index 79a8efd..caf48aa 100644 --- a/opsli-api/src/main/java/org/opsli/api/web/system/dict/DictApi.java +++ b/opsli-api/src/main/java/org/opsli/api/web/system/dict/DictApi.java @@ -1,7 +1,7 @@ package org.opsli.api.web.system.dict; import org.opsli.api.base.result.ResultVo; -import org.opsli.api.wrapper.system.dict.SysDictModel; +import org.opsli.api.wrapper.system.dict.DictModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -36,7 +36,7 @@ public interface DictApi { * @return ResultVo */ @GetMapping("/get") - ResultVo get(SysDictModel model); + ResultVo get(DictModel model); /** * 数据字典 查询分页 @@ -58,7 +58,7 @@ public interface DictApi { * @return ResultVo */ @PostMapping("/insert") - ResultVo insert(SysDictModel model); + ResultVo insert(DictModel model); /** * 数据字典 修改 @@ -66,7 +66,7 @@ public interface DictApi { * @return ResultVo */ @PostMapping("/update") - ResultVo update(SysDictModel model); + ResultVo update(DictModel model); /** * 数据字典 删除 diff --git a/opsli-api/src/main/java/org/opsli/api/web/system/dict/DictDetailApi.java b/opsli-api/src/main/java/org/opsli/api/web/system/dict/DictDetailApi.java index cd4f5a7..8021d41 100644 --- a/opsli-api/src/main/java/org/opsli/api/web/system/dict/DictDetailApi.java +++ b/opsli-api/src/main/java/org/opsli/api/web/system/dict/DictDetailApi.java @@ -1,7 +1,7 @@ package org.opsli.api.web.system.dict; import org.opsli.api.base.result.ResultVo; -import org.opsli.api.wrapper.system.dict.SysDictDetailModel; +import org.opsli.api.wrapper.system.dict.DictDetailModel; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -28,7 +28,6 @@ import java.util.List; */ public interface DictDetailApi { - /** 标题 */ String TITLE = "数据字典明细"; /** @@ -37,7 +36,7 @@ public interface DictDetailApi { * @return ResultVo */ @GetMapping("/get") - ResultVo get(SysDictDetailModel model); + ResultVo get(DictDetailModel model); /** * 数据字典 查询分页 @@ -59,7 +58,7 @@ public interface DictDetailApi { * @return ResultVo */ @PostMapping("/insert") - ResultVo insert(SysDictDetailModel model); + ResultVo insert(DictDetailModel model); /** * 数据字典 修改 @@ -67,7 +66,7 @@ public interface DictDetailApi { * @return ResultVo */ @PostMapping("/update") - ResultVo update(SysDictDetailModel model); + ResultVo update(DictDetailModel model); /** * 数据字典 删除 @@ -120,6 +119,6 @@ public interface DictDetailApi { * @return */ @GetMapping("/findListByTypeCode") - ResultVo> findListByTypeCode(String typeCode); + ResultVo> findListByTypeCode(String typeCode); } diff --git a/opsli-api/src/main/java/org/opsli/api/web/system/menu/MenuApi.java b/opsli-api/src/main/java/org/opsli/api/web/system/menu/MenuApi.java new file mode 100644 index 0000000..a422901 --- /dev/null +++ b/opsli-api/src/main/java/org/opsli/api/web/system/menu/MenuApi.java @@ -0,0 +1,113 @@ +package org.opsli.api.web.system.menu; + +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.api.wrapper.system.user.UserModel; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.web + * @Author: Parker + * @CreateTime: 2020-09-13 17:40 + * @Description: 菜单 API + * + * 对外 API 直接 暴露 @GetMapping 或者 @PostMapping + * 对内也推荐 单机版 不需要设置 Mapping 但是调用方法得从Controller写起 + * + * 这样写法虽然比较绕,但是当单体项目想要改造微服务架构时 时非常容易的 + * + * + */ +public interface MenuApi { + + /** 标题 */ + String TITLE = "菜单"; + + /** + * 菜单 查一条 + * @param model 模型 + * @return ResultVo + */ + @GetMapping("/get") + ResultVo get(MenuModel model); + + /** + * 菜单 查询分页 + * @param pageNo 当前页 + * @param pageSize 每页条数 + * @param request request + * @return ResultVo + */ + @GetMapping("/findPage") + ResultVo findPage( + @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, + HttpServletRequest request + ); + + /** + * 菜单 新增 + * @param model 模型 + * @return ResultVo + */ + @PostMapping("/insert") + ResultVo insert(MenuModel model); + + /** + * 菜单 修改 + * @param model 模型 + * @return ResultVo + */ + @PostMapping("/update") + ResultVo update(MenuModel model); + + /** + * 菜单 删除 + * @param id ID + * @return ResultVo + */ + @PostMapping("/del") + ResultVo del(String id); + + /** + * 菜单 批量删除 + * @param ids ID 数组 + * @return ResultVo + */ + @PostMapping("/delAll") + ResultVo delAll(String[] ids); + + /** + * 菜单 Excel 导出 + * @param request request + * @param response response + * @return ResultVo + */ + @GetMapping("/exportExcel") + ResultVo exportExcel(HttpServletRequest request, HttpServletResponse response); + + /** + * 菜单 Excel 导入 + * @param request 文件流 request + * @return ResultVo + */ + @GetMapping("/exportImport") + ResultVo excelImport(MultipartHttpServletRequest request); + + /** + * 菜单 Excel 下载导入模版 + * @param response response + * @return ResultVo + */ + @GetMapping("/exportImport/template") + ResultVo importTemplate(HttpServletResponse response); + +} diff --git a/opsli-api/src/main/java/org/opsli/api/web/system/role/RoleApi.java b/opsli-api/src/main/java/org/opsli/api/web/system/role/RoleApi.java new file mode 100644 index 0000000..4503d69 --- /dev/null +++ b/opsli-api/src/main/java/org/opsli/api/web/system/role/RoleApi.java @@ -0,0 +1,112 @@ +package org.opsli.api.web.system.role; + +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.wrapper.system.role.RoleModel; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.web + * @Author: Parker + * @CreateTime: 2020-09-13 17:40 + * @Description: 角色 API + * + * 对外 API 直接 暴露 @GetMapping 或者 @PostMapping + * 对内也推荐 单机版 不需要设置 Mapping 但是调用方法得从Controller写起 + * + * 这样写法虽然比较绕,但是当单体项目想要改造微服务架构时 时非常容易的 + * + * + */ +public interface RoleApi { + + /** 标题 */ + String TITLE = "角色"; + + /** + * 角色 查一条 + * @param model 模型 + * @return ResultVo + */ + @GetMapping("/get") + ResultVo get(RoleModel model); + + /** + * 角色 查询分页 + * @param pageNo 当前页 + * @param pageSize 每页条数 + * @param request request + * @return ResultVo + */ + @GetMapping("/findPage") + ResultVo findPage( + @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, + HttpServletRequest request + ); + + /** + * 角色 新增 + * @param model 模型 + * @return ResultVo + */ + @PostMapping("/insert") + ResultVo insert(RoleModel model); + + /** + * 角色 修改 + * @param model 模型 + * @return ResultVo + */ + @PostMapping("/update") + ResultVo update(RoleModel model); + + /** + * 角色 删除 + * @param id ID + * @return ResultVo + */ + @PostMapping("/del") + ResultVo del(String id); + + /** + * 角色 批量删除 + * @param ids ID 数组 + * @return ResultVo + */ + @PostMapping("/delAll") + ResultVo delAll(String[] ids); + + /** + * 角色 Excel 导出 + * @param request request + * @param response response + * @return ResultVo + */ + @GetMapping("/exportExcel") + ResultVo exportExcel(HttpServletRequest request, HttpServletResponse response); + + /** + * 角色 Excel 导入 + * @param request 文件流 request + * @return ResultVo + */ + @GetMapping("/exportImport") + ResultVo excelImport(MultipartHttpServletRequest request); + + /** + * 角色 Excel 下载导入模版 + * @param response response + * @return ResultVo + */ + @GetMapping("/exportImport/template") + ResultVo importTemplate(HttpServletResponse response); + +} diff --git a/opsli-api/src/main/java/org/opsli/api/web/system/user/UserApi.java b/opsli-api/src/main/java/org/opsli/api/web/system/user/UserApi.java new file mode 100644 index 0000000..3a23a58 --- /dev/null +++ b/opsli-api/src/main/java/org/opsli/api/web/system/user/UserApi.java @@ -0,0 +1,148 @@ +package org.opsli.api.web.system.user; + +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.api.wrapper.system.role.RoleModel; +import org.opsli.api.wrapper.system.user.UserModel; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.web + * @Author: Parker + * @CreateTime: 2020-09-13 17:40 + * @Description: 用户信息 API + * + * 对外 API 直接 暴露 @GetMapping 或者 @PostMapping + * 对内也推荐 单机版 不需要设置 Mapping 但是调用方法得从Controller写起 + * + * 这样写法虽然比较绕,但是当单体项目想要改造微服务架构时 时非常容易的 + * + * + */ +public interface UserApi { + + /** 标题 */ + String TITLE = "用户信息"; + + /** + * 用户信息 查一条 + * @param model 模型 + * @return ResultVo + */ + @GetMapping("/get") + ResultVo get(UserModel model); + + /** + * 用户信息 查询分页 + * @param pageNo 当前页 + * @param pageSize 每页条数 + * @param request request + * @return ResultVo + */ + @GetMapping("/findPage") + ResultVo findPage( + @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo, + @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, + HttpServletRequest request + ); + + /** + * 用户信息 新增 + * @param model 模型 + * @return ResultVo + */ + @PostMapping("/insert") + ResultVo insert(UserModel model); + + /** + * 用户信息 修改 + * @param model 模型 + * @return ResultVo + */ + @PostMapping("/update") + ResultVo update(UserModel model); + + /** + * 用户信息 删除 + * @param id ID + * @return ResultVo + */ + @PostMapping("/del") + ResultVo del(String id); + + /** + * 用户信息 批量删除 + * @param ids ID 数组 + * @return ResultVo + */ + @PostMapping("/delAll") + ResultVo delAll(String[] ids); + + /** + * 用户信息 Excel 导出 + * @param request request + * @param response response + * @return ResultVo + */ + @GetMapping("/exportExcel") + ResultVo exportExcel(HttpServletRequest request, HttpServletResponse response); + + /** + * 用户信息 Excel 导入 + * @param request 文件流 request + * @return ResultVo + */ + @GetMapping("/exportImport") + ResultVo excelImport(MultipartHttpServletRequest request); + + /** + * 用户信息 Excel 下载导入模版 + * @param response response + * @return ResultVo + */ + @GetMapping("/exportImport/template") + ResultVo importTemplate(HttpServletResponse response); + + + /** + * 根据 username 获得用户 + * @param username 用户名 + * @return ResultVo + */ + //@GetMapping("/getUserByUsername") + ResultVo getUserByUsername(String username); + + /** + * 根据 userId 获得用户角色 + * @param userId 用户Id + * @return ResultVo + */ + //@GetMapping("/getRolesByUserId") + ResultVo> getRolesByUserId(String userId); + + /** + * 根据 userId 获得用户权限 + * @param userId 用户Id + * @return ResultVo + */ + //@GetMapping("/queryAllPerms") + ResultVo> getAllPerms(String userId); + + /** + * 根据 userId 获得用户菜单 + * @param userId 用户Id + * @return ResultVo + */ + //@GetMapping("/queryAllPerms") + ResultVo> getMenuListByUserId(String userId); + +} diff --git a/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/SysDictDetailModel.java b/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictDetailModel.java similarity index 86% rename from opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/SysDictDetailModel.java rename to opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictDetailModel.java index d7000e4..95d8d6e 100644 --- a/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/SysDictDetailModel.java +++ b/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictDetailModel.java @@ -9,7 +9,7 @@ import org.opsli.api.base.warpper.ApiWrapper; import org.opsli.common.annotation.validation.ValidationArgs; import org.opsli.common.annotation.validation.ValidationArgsMax; import org.opsli.common.enums.ValiArgsType; -import org.opsli.plugins.excel.annotation.CellStyleFormat; +import org.opsli.plugins.excel.annotation.ExcelInfo; /** * @BelongsProject: opsli-boot @@ -20,7 +20,7 @@ import org.opsli.plugins.excel.annotation.CellStyleFormat; */ @Data @EqualsAndHashCode(callSuper = false) -public class SysDictDetailModel extends ApiWrapper { +public class DictDetailModel extends ApiWrapper { /** 字典ID */ @@ -40,7 +40,7 @@ public class SysDictDetailModel extends ApiWrapper { /** 字典名称 */ @ApiModelProperty(value = "字典名称") @ExcelProperty(value = "字典名称", order = 1) - @CellStyleFormat + @ExcelInfo // 验证器 @ValidationArgs({ValiArgsType.IS_NOT_NULL, ValiArgsType.IS_GENERAL_WITH_CHINESE}) @ValidationArgsMax(120) @@ -49,32 +49,34 @@ public class SysDictDetailModel extends ApiWrapper { /** 字典值 */ @ApiModelProperty(value = "字典值") @ExcelProperty(value = "字典值", order = 2) - @CellStyleFormat + @ExcelInfo // 验证器 @ValidationArgs({ValiArgsType.IS_NOT_NULL}) @ValidationArgsMax(120) private String dictValue; - /** 是否内置数据 */ - @ApiModelProperty(value = "是否内置数据") + /** 是否内置数据 0是 1否*/ + @ApiModelProperty(value = "是否内置数据 0是 1否") @ExcelProperty(value = "是否内置数据", order = 2) - @CellStyleFormat + @ExcelInfo(dictType = "yes_no") // 验证器 @ValidationArgs({ValiArgsType.IS_NOT_NULL}) + @ValidationArgsMax(1) private Character izLock; /** 排序 */ @ApiModelProperty(value = "排序") @ExcelProperty(value = "排序", order = 2) - @CellStyleFormat + @ExcelInfo // 验证器 @ValidationArgs({ValiArgsType.IS_NOT_NULL, ValiArgsType.IS_NUMBER}) + @ValidationArgsMax(10) private Integer sortNo; /** 备注 */ @ApiModelProperty(value = "备注") @ExcelProperty(value = "备注", order = 2) - @CellStyleFormat + @ExcelInfo // 验证器 @ValidationArgsMax(255) private String remark; diff --git a/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictModel.java b/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictModel.java index 918ba5c..b454126 100644 --- a/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictModel.java +++ b/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictModel.java @@ -1,29 +1,62 @@ package org.opsli.api.wrapper.system.dict; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; +import org.opsli.api.base.warpper.ApiWrapper; +import org.opsli.common.annotation.validation.ValidationArgs; +import org.opsli.common.annotation.validation.ValidationArgsMax; +import org.opsli.common.enums.ValiArgsType; +import org.opsli.plugins.excel.annotation.ExcelInfo; /** * @BelongsProject: opsli-boot * @BelongsPackage: org.opsli.modulars.test.entity * @Author: Parker * @CreateTime: 2020-09-16 17:33 - * @Description: 数据字典 - 工具类用 + * @Description: 数据字典 */ @Data @EqualsAndHashCode(callSuper = false) -public class DictModel { +public class DictModel extends ApiWrapper { - /** 类型编号 - 冗余 */ + + + /** 字典类型编号 */ + @ApiModelProperty(value = "字典类型编号") + @ExcelProperty(value = "字典类型编号", order = 1) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL, ValiArgsType.IS_GENERAL}) + @ValidationArgsMax(120) private String typeCode; - /** 字典名称 */ - private String dictName; + /** 字典类型名称 */ + @ApiModelProperty(value = "字典类型名称") + @ExcelProperty(value = "字典类型名称", order = 2) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL, ValiArgsType.IS_GENERAL_WITH_CHINESE}) + @ValidationArgsMax(120) + private String typeName; + + /** 是否内置数据 0是 1否*/ + @ApiModelProperty(value = "是否内置数据 0是 1否") + @ExcelProperty(value = "是否内置数据", order = 3) + @ExcelInfo(dictType = "yes_no") + // 验证器 + @ValidationArgs(ValiArgsType.IS_NOT_NULL) + @ValidationArgsMax(1) + private Character izLock; - /** 字典值 */ - private String dictValue; + /** 备注 */ + @ApiModelProperty(value = "备注") + @ExcelProperty(value = "备注", order = 4) + @ExcelInfo + // 验证器 + @ValidationArgsMax(255) + private String remark; - /** 消息 */ - private SysDictDetailModel model; } diff --git a/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictWrapper.java b/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictWrapper.java new file mode 100644 index 0000000..83b37ac --- /dev/null +++ b/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/DictWrapper.java @@ -0,0 +1,29 @@ +package org.opsli.api.wrapper.system.dict; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.entity + * @Author: Parker + * @CreateTime: 2020-09-16 17:33 + * @Description: 数据字典 - 工具类用 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class DictWrapper { + + /** 类型编号 - 冗余 */ + private String typeCode; + + /** 字典名称 */ + private String dictName; + + /** 字典值 */ + private String dictValue; + + /** 消息 */ + private DictDetailModel model; + +} diff --git a/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/SysDictModel.java b/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/SysDictModel.java deleted file mode 100644 index 2908f6c..0000000 --- a/opsli-api/src/main/java/org/opsli/api/wrapper/system/dict/SysDictModel.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.opsli.api.wrapper.system.dict; - -import com.alibaba.excel.annotation.ExcelProperty; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.opsli.api.base.warpper.ApiWrapper; -import org.opsli.common.annotation.validation.ValidationArgs; -import org.opsli.common.annotation.validation.ValidationArgsMax; -import org.opsli.common.enums.ValiArgsType; -import org.opsli.plugins.excel.annotation.CellStyleFormat; - -/** - * @BelongsProject: opsli-boot - * @BelongsPackage: org.opsli.modulars.test.entity - * @Author: Parker - * @CreateTime: 2020-09-16 17:33 - * @Description: 数据字典 - */ -@Data -@EqualsAndHashCode(callSuper = false) -public class SysDictModel extends ApiWrapper { - - - - /** 字典类型编号 */ - @ApiModelProperty(value = "字典类型编号") - @ExcelProperty(value = "字典类型编号", order = 1) - @CellStyleFormat - // 验证器 - @ValidationArgs({ValiArgsType.IS_NOT_NULL, ValiArgsType.IS_GENERAL}) - @ValidationArgsMax(120) - private String typeCode; - - /** 字典类型名称 */ - @ApiModelProperty(value = "字典类型名称") - @ExcelProperty(value = "字典类型名称", order = 2) - @CellStyleFormat - // 验证器 - @ValidationArgs({ValiArgsType.IS_NOT_NULL, ValiArgsType.IS_GENERAL_WITH_CHINESE}) - @ValidationArgsMax(120) - private String typeName; - - /** 是否内置数据 */ - @ApiModelProperty(value = "是否内置数据") - @ExcelProperty(value = "是否内置数据", order = 3) - @CellStyleFormat - // 验证器 - @ValidationArgs(ValiArgsType.IS_NOT_NULL) - private Character izLock; - - /** 备注 */ - @ApiModelProperty(value = "备注") - @ExcelProperty(value = "备注", order = 4) - @CellStyleFormat - // 验证器 - @ValidationArgsMax(255) - private String remark; - - -} diff --git a/opsli-api/src/main/java/org/opsli/api/wrapper/system/menu/MenuModel.java b/opsli-api/src/main/java/org/opsli/api/wrapper/system/menu/MenuModel.java new file mode 100644 index 0000000..1ff466c --- /dev/null +++ b/opsli-api/src/main/java/org/opsli/api/wrapper/system/menu/MenuModel.java @@ -0,0 +1,77 @@ +package org.opsli.api.wrapper.system.menu; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.opsli.api.base.warpper.ApiWrapper; +import org.opsli.common.annotation.validation.ValidationArgs; +import org.opsli.common.annotation.validation.ValidationArgsMax; +import org.opsli.common.enums.ValiArgsType; +import org.opsli.plugins.excel.annotation.ExcelInfo; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.entity + * @Author: Parker + * @CreateTime: 2020-09-16 17:33 + * @Description: 菜单表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class MenuModel extends ApiWrapper { + + /** 父级主键 */ + @ApiModelProperty(value = "父级主键") + @ExcelProperty(value = "父级主键", order = 1) + @ExcelInfo + // 验证器 + @ValidationArgs(ValiArgsType.IS_NOT_NULL) + @ValidationArgsMax(20) + private String parentId; + + /** 菜单编号 */ + @ApiModelProperty(value = "菜单编号") + @ExcelProperty(value = "菜单编号", order = 2) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL,ValiArgsType.IS_GENERAL}) + @ValidationArgsMax(50) + private String menuCode; + + /** 菜单名称 */ + @ApiModelProperty(value = "菜单名称") + @ExcelProperty(value = "菜单名称", order = 3) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL,ValiArgsType.IS_GENERAL_WITH_CHINESE}) + @ValidationArgsMax(50) + private String menuName; + + /** 图标 */ + @ApiModelProperty(value = "图标") + @ExcelProperty(value = "图标", order = 4) + @ExcelInfo + // 验证器 + @ValidationArgsMax(50) + private String icon; + + /** 项目类型:1-菜单2-按钮3-链接4-表单 */ + @ApiModelProperty(value = "项目类型:1-菜单2-按钮3-链接4-表单") + @ExcelProperty(value = "项目类型", order = 5) + @ExcelInfo(dictType = "menuType") + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL}) + @ValidationArgsMax(20) + private String type; + + /** url地址 */ + @ApiModelProperty(value = "url地址") + @ExcelProperty(value = "url地址", order = 6) + @ExcelInfo + // 验证器 + @ValidationArgsMax(200) + private String url; + + +} diff --git a/opsli-api/src/main/java/org/opsli/api/wrapper/system/role/RoleModel.java b/opsli-api/src/main/java/org/opsli/api/wrapper/system/role/RoleModel.java new file mode 100644 index 0000000..05e30b2 --- /dev/null +++ b/opsli-api/src/main/java/org/opsli/api/wrapper/system/role/RoleModel.java @@ -0,0 +1,61 @@ +package org.opsli.api.wrapper.system.role; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.opsli.api.base.warpper.ApiWrapper; +import org.opsli.common.annotation.validation.ValidationArgs; +import org.opsli.common.annotation.validation.ValidationArgsMax; +import org.opsli.common.enums.ValiArgsType; +import org.opsli.plugins.excel.annotation.ExcelInfo; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.entity + * @Author: Parker + * @CreateTime: 2020-09-16 17:33 + * @Description: 角色表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class RoleModel extends ApiWrapper { + + + + /** 角色编码 */ + @ApiModelProperty(value = "角色编码") + @ExcelProperty(value = "角色编码", order = 1) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL,ValiArgsType.IS_GENERAL}) + @ValidationArgsMax(50) + private String roleCode; + + /** 角色名称 */ + @ApiModelProperty(value = "角色编码") + @ExcelProperty(value = "角色编码", order = 2) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL,ValiArgsType.IS_GENERAL}) + @ValidationArgsMax(50) + private String roleName; + + /** 是否内置数据 0是 1否*/ + @ApiModelProperty(value = "是否内置数据 0是 1否") + @ExcelProperty(value = "是否内置数据", order = 3) + @ExcelInfo(dictType = "yes_no") + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL,ValiArgsType.IS_GENERAL}) + @ValidationArgsMax(1) + private Character izLock; + + /** 备注 */ + @ApiModelProperty(value = "备注") + @ExcelProperty(value = "备注", order = 4) + @ExcelInfo + // 验证器 + @ValidationArgsMax(255) + private String remark; + +} diff --git a/opsli-api/src/main/java/org/opsli/api/wrapper/system/user/UserModel.java b/opsli-api/src/main/java/org/opsli/api/wrapper/system/user/UserModel.java new file mode 100644 index 0000000..eab9147 --- /dev/null +++ b/opsli-api/src/main/java/org/opsli/api/wrapper/system/user/UserModel.java @@ -0,0 +1,121 @@ +package org.opsli.api.wrapper.system.user; + +import com.alibaba.excel.annotation.ExcelIgnore; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.opsli.api.base.warpper.ApiWrapper; +import org.opsli.common.annotation.validation.ValidationArgs; +import org.opsli.common.annotation.validation.ValidationArgsMax; +import org.opsli.common.enums.ValiArgsType; +import org.opsli.plugins.excel.annotation.ExcelInfo; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.entity + * @Author: Parker + * @CreateTime: 2020-09-16 17:33 + * @Description: 用户信息表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class UserModel extends ApiWrapper { + + + /** 登录账户 */ + @ApiModelProperty(value = "登录账户") + @ExcelIgnore + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL,ValiArgsType.IS_GENERAL}) + @ValidationArgsMax(32) + private String username; + + /** 登录密码 */ + @ApiModelProperty(value = "登录密码") + @ExcelIgnore + // 验证器 + @ValidationArgsMax(50) + private String password; + + /** 盐值,密码秘钥 */ + @ApiModelProperty(value = "盐值,密码秘钥") + @ExcelIgnore + // 验证器 + @ValidationArgsMax(50) + private String secretkey; + + /** 是否锁定 */ + @ApiModelProperty(value = "是否锁定") + @ExcelIgnore + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL}) + @ValidationArgsMax(1) + private Character locked; + + /** 真实姓名 */ + @ApiModelProperty(value = "真实姓名") + @ExcelProperty(value = "真实姓名", order = 1) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_NOT_NULL,ValiArgsType.IS_GENERAL_WITH_CHINESE}) + @ValidationArgsMax(50) + private String realName; + + /** 手机 */ + @ApiModelProperty(value = "手机") + @ExcelProperty(value = "手机", order = 2) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_MOBILE}) + private String mobile; + + /** 邮箱 */ + @ApiModelProperty(value = "邮箱") + @ExcelProperty(value = "邮箱", order = 3) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_EMAIL}) + private String email; + + /** 工号 */ + @ApiModelProperty(value = "工号") + @ExcelProperty(value = "工号", order = 4) + @ExcelInfo + // 验证器 + @ValidationArgs({ValiArgsType.IS_GENERAL}) + @ValidationArgsMax(32) + private String no; + + /** 头像 */ + @ApiModelProperty(value = "头像") + @ExcelIgnore + // 验证器 + @ValidationArgsMax(255) + private String avatar; + + /** 最后登陆IP */ + @ApiModelProperty(value = "最后登陆IP") + @ExcelIgnore + // 验证器 + @ValidationArgs(ValiArgsType.IS_IPV4) + private String loginIp; + + /** 备注 */ + @ApiModelProperty(value = "备注") + @ExcelProperty(value = "备注", order = 5) + @ExcelInfo + // 验证器 + @ValidationArgsMax(255) + private String remark; + + + /** 多租户字段 */ + @ApiModelProperty(value = "多租户ID") + @ExcelIgnore + // 验证器 + @ValidationArgsMax(20) + private String tenantId; + + +} diff --git a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/DictType.java b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/DictType.java deleted file mode 100644 index 2b957af..0000000 --- a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/DictType.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.opsli.common.annotation; - - -import java.lang.annotation.*; - -/** - * @BelongsProject: opsli-boot - * @BelongsPackage: org.opsli.common.annotation - * @Author: Parker - * @CreateTime: 2020-09-16 16:36 - * @Description: 字典标示 - - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -@Documented -public @interface DictType { - - /** 字典类型 code */ - String value(); - -} diff --git a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/EnableHotData.java b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/EnableHotData.java similarity index 95% rename from opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/EnableHotData.java rename to opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/EnableHotData.java index b25e88a..aea5b06 100644 --- a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/EnableHotData.java +++ b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/EnableHotData.java @@ -1,4 +1,4 @@ -package org.opsli.common.annotation; +package org.opsli.common.annotation.hotdata; import java.lang.annotation.*; diff --git a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/HotDataDel.java b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/HotDataDel.java similarity index 95% rename from opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/HotDataDel.java rename to opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/HotDataDel.java index 4fa5cfb..b6141b0 100644 --- a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/HotDataDel.java +++ b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/HotDataDel.java @@ -1,4 +1,4 @@ -package org.opsli.common.annotation; +package org.opsli.common.annotation.hotdata; import org.opsli.common.constants.CacheConstants; diff --git a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/HotDataPut.java b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/HotDataPut.java similarity index 95% rename from opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/HotDataPut.java rename to opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/HotDataPut.java index c152170..940e59d 100644 --- a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/HotDataPut.java +++ b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/annotation/hotdata/HotDataPut.java @@ -1,4 +1,4 @@ -package org.opsli.common.annotation; +package org.opsli.common.annotation.hotdata; import org.opsli.common.constants.CacheConstants; diff --git a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/api/TokenThreadLocal.java b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/api/TokenThreadLocal.java new file mode 100644 index 0000000..1c77c73 --- /dev/null +++ b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/api/TokenThreadLocal.java @@ -0,0 +1,30 @@ +package org.opsli.common.api; + + +/** + * 用于存放当前线程下 Token + * + * @author parker + * @date 2020-09-15 + */ +public class TokenThreadLocal { + + /** 临时线程存储 token 容器 */ + private static final ThreadLocal tokenData = new ThreadLocal<>(); + + public static void put(String token) { + if (tokenData.get() == null) { + tokenData.set(token); + } + } + + public static String get() { + return tokenData.get(); + } + + public static void remove() { + try { + tokenData.remove(); + }catch (Exception e){} + } +} diff --git a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/OrderConstants.java b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/OrderConstants.java index c4d8d25..2369d2c 100644 --- a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/OrderConstants.java +++ b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/OrderConstants.java @@ -7,6 +7,8 @@ package org.opsli.common.constants; */ public interface OrderConstants { + /** token */ + int TOKEN_AOP_SORT = 150; /** 热点数据加载顺序 */ int HOT_DATA_ORDER = 180; diff --git a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/SignConstants.java b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/SignConstants.java new file mode 100644 index 0000000..3289143 --- /dev/null +++ b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/SignConstants.java @@ -0,0 +1,21 @@ +package org.opsli.common.constants; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.common.constants + * @Author: Parker + * @CreateTime: 2020-09-16 17:42 + * @Description: 签名 + */ +public interface SignConstants { + + /** username */ + String ACCOUNT = "account"; + + /** user Id */ + String USER_ID = "userId"; + + /** 时间戳 */ + String TIMESTAMP = "timestamp"; + +} diff --git a/opsli-base-support/opsli-core/pom.xml b/opsli-base-support/opsli-core/pom.xml index f48e56a..26002c3 100644 --- a/opsli-base-support/opsli-core/pom.xml +++ b/opsli-base-support/opsli-core/pom.xml @@ -47,7 +47,44 @@ ${plugins.version} + + + org.crazycake + shiro-redis-spring-boot-starter + 3.3.1 + + + + + + + + + + + + + + + + + + + + + + + com.github.axet + kaptcha + + + + + com.auth0 + java-jwt + + diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/CacheDataAop.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/CacheDataAop.java index 22cee14..b66313a 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/CacheDataAop.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/CacheDataAop.java @@ -8,9 +8,9 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.opsli.api.base.warpper.ApiWrapper; -import org.opsli.common.annotation.EnableHotData; -import org.opsli.common.annotation.HotDataDel; -import org.opsli.common.annotation.HotDataPut; +import org.opsli.common.annotation.hotdata.EnableHotData; +import org.opsli.common.annotation.hotdata.HotDataDel; +import org.opsli.common.annotation.hotdata.HotDataPut; import org.opsli.common.constants.CacheConstants; import org.opsli.core.cache.local.CacheUtil; import org.opsli.core.cache.pushsub.entity.CacheDataEntity; @@ -47,11 +47,11 @@ public class CacheDataAop { @Autowired private RedisPlugin redisPlugin; - @Pointcut("@annotation(org.opsli.common.annotation.HotDataPut)") + @Pointcut("@annotation(org.opsli.common.annotation.hotdata.HotDataPut)") public void hotDataPut() { } - @Pointcut("@annotation(org.opsli.common.annotation.HotDataDel)") + @Pointcut("@annotation(org.opsli.common.annotation.hotdata.HotDataDel)") public void hotDataDel() { } diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/TokenAop.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/TokenAop.java new file mode 100644 index 0000000..3ac415e --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/TokenAop.java @@ -0,0 +1,76 @@ +package org.opsli.core.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.opsli.common.api.TokenThreadLocal; +import org.opsli.common.exception.ServiceException; +import org.opsli.core.utils.UserTokenUtil; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +import static org.opsli.common.constants.OrderConstants.TOKEN_AOP_SORT; + +/** + * 参数校验 拦截处理 + * + * @author parker + * @date 2020-09-16 + */ +@Slf4j +@Order(TOKEN_AOP_SORT) +@Aspect +@Component +public class TokenAop { + + + @Pointcut("execution(public * org.opsli.modulars*..*.*Controller*.*(..))") + public void requestMapping() { + } + + /** + * 切如 post 请求 + * @param point + */ + @Around("requestMapping()") + public Object tokenAop(ProceedingJoinPoint point) throws Throwable { + + // 将 Token放入 线程缓存 + try { + RequestAttributes ra = RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes sra = (ServletRequestAttributes) ra; + HttpServletRequest request = sra.getRequest(); + String requestToken = UserTokenUtil.getRequestToken(request); + if(StringUtils.isNotEmpty(requestToken)){ + // 放入当前线程缓存中 + TokenThreadLocal.put(requestToken); + } + }catch (ServiceException e){ + throw e; + }catch (Exception e){ + log.error(e.getMessage(),e); + } + + // 防止线程抛异常 线程变量不回收 导致oom + Object returnValue = null; + try { + // 执行正常操作 + Object[] args= point.getArgs(); + returnValue = point.proceed(args); + } finally { + // 线程销毁时 删除 token + TokenThreadLocal.remove(); + } + + return returnValue; + } + +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/ValitaionArgsAop.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/ValitaionArgsAop.java index b6d2169..ef774e1 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/ValitaionArgsAop.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/aspect/ValitaionArgsAop.java @@ -2,27 +2,19 @@ package org.opsli.core.aspect; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; -import org.aspectj.lang.reflect.MethodSignature; import org.opsli.api.base.warpper.ApiWrapper; import org.opsli.api.utils.ValidationUtil; -import org.opsli.common.annotation.HotDataPut; import org.opsli.common.exception.ServiceException; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; -import java.lang.reflect.Method; import static org.opsli.common.constants.OrderConstants.PARAM_VALIDATE_AOP_SORT; diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/base/concroller/BaseRestController.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/base/concroller/BaseRestController.java index a8aa53b..01c2da8 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/base/concroller/BaseRestController.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/base/concroller/BaseRestController.java @@ -13,7 +13,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.opsli.api.base.result.ResultVo; import org.opsli.api.base.warpper.ApiWrapper; -import org.opsli.common.annotation.EnableHotData; +import org.opsli.common.annotation.hotdata.EnableHotData; import org.opsli.common.exception.ServiceException; import org.opsli.common.msg.CommonMsg; import org.opsli.common.utils.WrapperUtil; @@ -104,16 +104,21 @@ public abstract class BaseRestController dicts = (Collection) msgJson.get(MsgArgsType.DICT_MODELS.toString()); for (Object dictObj : dicts) { JSONObject jsonObject = (JSONObject) dictObj; - DictModel dictModel = JSONObject.toJavaObject(jsonObject, DictModel.class); - this.handler(dictModel, type); + DictWrapper dictWrapperModel = JSONObject.toJavaObject(jsonObject, DictWrapper.class); + this.handler(dictWrapperModel, type); } } else if(DictModelType.OBJECT == dictModelType){ Object dictObj = msgJson.get(MsgArgsType.DICT_MODEL.toString()); JSONObject jsonObject = (JSONObject) dictObj; - DictModel dictModel = JSONObject.toJavaObject(jsonObject, DictModel.class); - this.handler(dictModel, type); + DictWrapper dictWrapperModel = JSONObject.toJavaObject(jsonObject, DictWrapper.class); + this.handler(dictWrapperModel, type); } } /** * 真正处理 - 只是处理自己本地的缓存 - * @param dictModel + * @param dictWrapperModel * @param type */ - private void handler(DictModel dictModel, CacheType type){ + private void handler(DictWrapper dictWrapperModel, CacheType type){ // 解析 key String ehKeyByName = CacheUtil.handleKey(CacheConstants.EDEN_HASH_DATA, - DictConstants.CACHE_PREFIX_NAME + dictModel.getTypeCode() + ":" + dictModel.getDictName()); + DictConstants.CACHE_PREFIX_NAME + dictWrapperModel.getTypeCode() + ":" + dictWrapperModel.getDictName()); String ehKeyByValue = CacheUtil.handleKey(CacheConstants.EDEN_HASH_DATA, - DictConstants.CACHE_PREFIX_VALUE + dictModel.getTypeCode() + ":" + dictModel.getDictValue()); + DictConstants.CACHE_PREFIX_VALUE + dictWrapperModel.getTypeCode() + ":" + dictWrapperModel.getDictValue()); // 缓存更新 if(CacheType.UPDATE == type){ @@ -74,7 +73,7 @@ public class DictHandler implements RedisPushSubHandler{ ehCachePlugin.delete(CacheConstants.HOT_DATA, ehKeyByValue); // 统一转换为 JSONObject - String jsonStr = JSONObject.toJSONString(dictModel.getModel()); + String jsonStr = JSONObject.toJSONString(dictWrapperModel.getModel()); JSONObject value = JSONObject.parseObject(jsonStr); ehCachePlugin.put(CacheConstants.HOT_DATA, ehKeyByName, value); ehCachePlugin.put(CacheConstants.HOT_DATA, ehKeyByValue, value); diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/cache/pushsub/msgs/DictMsgFactory.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/cache/pushsub/msgs/DictMsgFactory.java index 1423e2b..cfc2dc9 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/cache/pushsub/msgs/DictMsgFactory.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/cache/pushsub/msgs/DictMsgFactory.java @@ -3,7 +3,7 @@ package org.opsli.core.cache.pushsub.msgs; import com.alibaba.fastjson.JSONObject; import lombok.Data; import lombok.experimental.Accessors; -import org.opsli.api.wrapper.system.dict.DictModel; +import org.opsli.api.wrapper.system.dict.DictWrapper; import org.opsli.core.cache.pushsub.enums.CacheType; import org.opsli.core.cache.pushsub.enums.DictModelType; import org.opsli.core.cache.pushsub.enums.MsgArgsType; @@ -33,11 +33,11 @@ public final class DictMsgFactory extends BaseSubMessage{ /** * 构建消息 */ - public static BaseSubMessage createMsg(DictModel dictModel, CacheType cacheType){ + public static BaseSubMessage createMsg(DictWrapper dictWrapperModel, CacheType cacheType){ BaseSubMessage baseSubMessage = new BaseSubMessage(); // 数据 JSONObject jsonObj = new JSONObject(); - jsonObj.put(MsgArgsType.DICT_MODEL.toString(),dictModel); + jsonObj.put(MsgArgsType.DICT_MODEL.toString(), dictWrapperModel); jsonObj.put(MsgArgsType.DICT_MODEL_TYPE.toString(), DictModelType.OBJECT); jsonObj.put(MsgArgsType.DICT_TYPE.toString(),cacheType.toString()); @@ -49,11 +49,11 @@ public final class DictMsgFactory extends BaseSubMessage{ /** * 构建消息 */ - public static BaseSubMessage createMsg(List dictModels, CacheType cacheType){ + public static BaseSubMessage createMsg(List dictWrapperModels, CacheType cacheType){ BaseSubMessage baseSubMessage = new BaseSubMessage(); // 数据 JSONObject jsonObj = new JSONObject(); - jsonObj.put(MsgArgsType.DICT_MODELS.toString(),dictModels); + jsonObj.put(MsgArgsType.DICT_MODELS.toString(), dictWrapperModels); jsonObj.put(MsgArgsType.DICT_MODEL_TYPE.toString(), DictModelType.COLLECTION); jsonObj.put(MsgArgsType.DICT_TYPE.toString(),cacheType.toString()); diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/KaptchaConfig.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/KaptchaConfig.java new file mode 100644 index 0000000..b8c264f --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/KaptchaConfig.java @@ -0,0 +1,34 @@ +package org.opsli.core.conf; + +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Properties; + + +/** + * 生成验证码配置 + * + * @author 孙志强 + + * @date 2017-04-20 19:22 + */ +@Configuration +public class KaptchaConfig { + + @Bean + public DefaultKaptcha producer() { + Properties properties = new Properties(); + properties.put("kaptcha.border", "no"); + properties.put("kaptcha.textproducer.font.color", "black"); + properties.put("kaptcha.textproducer.char.space", "10"); + properties.put("kaptcha.textproducer.char.string", "1234567890"); + properties.put("kaptcha.textproducer.char.length", "4"); + Config config = new Config(properties); + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/RedisMessageListenerConfig.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/RedisMessageListenerConfig.java index 4bd237d..24c6755 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/RedisMessageListenerConfig.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/RedisMessageListenerConfig.java @@ -2,7 +2,8 @@ package org.opsli.core.conf; import lombok.extern.slf4j.Slf4j; import org.opsli.core.cache.pushsub.receiver.RedisPushSubReceiver; -import org.opsli.plugins.redis.pushsub.receiver.BaseReceiver; +import org.opsli.plugins.redis.conf.RedisPluginConfig; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,7 +12,6 @@ import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; -import javax.annotation.Resource; /** * @author : parker @@ -22,11 +22,10 @@ import javax.annotation.Resource; **/ @Slf4j @Configuration +@AutoConfigureAfter(RedisPluginConfig.class) @ConditionalOnProperty(name = "spring.redis.pushsub.enable", havingValue = "true") public class RedisMessageListenerConfig { - @Resource - private LettuceConnectionFactory factory; /** * redis消息监听器容器 @@ -36,10 +35,10 @@ public class RedisMessageListenerConfig { */ @Bean - public RedisMessageListenerContainer container() { + public RedisMessageListenerContainer container(LettuceConnectionFactory lettuceConnectionFactory) { RedisPushSubReceiver receiver = new RedisPushSubReceiver(); RedisMessageListenerContainer container = new RedisMessageListenerContainer(); - container.setConnectionFactory(factory); + container.setConnectionFactory(lettuceConnectionFactory); //订阅了的通道 container.addMessageListener(listenerAdapter(new RedisPushSubReceiver()), new PatternTopic(receiver.getListenerChannel())); diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/ShiroConfig.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/ShiroConfig.java new file mode 100644 index 0000000..8e0868c --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/ShiroConfig.java @@ -0,0 +1,125 @@ +package org.opsli.core.conf; + +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.opsli.core.security.shiro.filter.OAuth2Filter; +import org.opsli.core.security.shiro.realm.OAuth2Realm; +import org.opsli.plugins.redis.conf.RedisPluginConfig; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Shiro配置 + * + * 全程用 token认证 所以也就用不着 什么共享缓存 - 无状态 + * + * @author parker + * @date 2017-04-20 18:33 + */ +@Configuration +@AutoConfigureAfter(RedisPluginConfig.class) +public class ShiroConfig { + + + /** + * filer + * @param securityManager + * @return + */ + @Bean("shiroFilter") + public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); + shiroFilter.setSecurityManager(securityManager); + + //oauth过滤 + Map filters = new HashMap<>(); + filters.put("oauth2", new OAuth2Filter()); + shiroFilter.setFilters(filters); + + Map filterMap = new LinkedHashMap<>(); + filterMap.put("/webjars/**", "anon"); + filterMap.put("/druid/**", "anon"); + filterMap.put("/app/**", "anon"); + filterMap.put("/sys/login", "anon"); + filterMap.put("/swagger/**", "anon"); + filterMap.put("/v2/api-docs", "anon"); + filterMap.put("/doc.html", "anon"); + filterMap.put("/swagger-ui.html", "anon"); + filterMap.put("/swagger-resources/**", "anon"); + filterMap.put("/captcha.jpg", "anon"); + filterMap.put("/ueditor/**", "anon"); + filterMap.put("/**", "oauth2"); + shiroFilter.setFilterChainDefinitionMap(filterMap); + + return shiroFilter; + } + + @Bean("sessionManager") + public SessionManager sessionManager(){ + DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); + sessionManager.setSessionValidationSchedulerEnabled(true); + sessionManager.setSessionIdCookieEnabled(true); + return sessionManager; + } + + @Bean("securityManager") + public DefaultWebSecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager,LettuceConnectionFactory lettuceConnectionFactory) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(oAuth2Realm); + securityManager.setSessionManager(sessionManager); + return securityManager; + } + + @Bean + public OAuth2Realm oAuth2Realm() { + return new OAuth2Realm(); + } + + + + // ===================== 固定三板斧 ===================== + // 其实 没有 Spring Security 配置起来简单 + // + + @Bean("lifecycleBeanPostProcessor") + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + @Bean("defaultAdvisorAutoProxyCreator") + @DependsOn("lifecycleBeanPostProcessor") + public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator(); + proxyCreator.setProxyTargetClass(true); + proxyCreator.setUsePrefix(true); + proxyCreator.setAdvisorBeanNamePrefix("_no_advisor"); + return proxyCreator; + } + + /** + * 开启shiro权限注解生效 + * @param securityManager + * @return + */ + @Bean("authorizationAttributeSourceAdvisor") + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); + advisor.setSecurityManager(securityManager); + return advisor; + } + +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/mybatis/AutoFillInterceptor.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/mybatis/AutoFillInterceptor.java index 91ec43e..b545a57 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/mybatis/AutoFillInterceptor.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/mybatis/AutoFillInterceptor.java @@ -8,6 +8,7 @@ import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.*; +import org.opsli.api.wrapper.system.user.UserModel; import org.opsli.common.constants.MyBatisConstants; import org.opsli.core.utils.UserUtil; import org.springframework.stereotype.Component; @@ -79,13 +80,14 @@ public class AutoFillInterceptor implements Interceptor { * @param arg */ public void insertFill(Object arg) { + UserModel userModel = UserUtil.getUser(); Field[] fields = ReflectUtil.getFields(arg.getClass()); for (Field f : fields) { f.setAccessible(true); switch (f.getName()) { // 创建人 case MyBatisConstants.FIELD_CREATE_BY: - setProperty(arg, MyBatisConstants.FIELD_CREATE_BY, "测试"); + setProperty(arg, MyBatisConstants.FIELD_CREATE_BY, userModel.getId()); break; // 创建日期 case MyBatisConstants.FIELD_CREATE_TIME: @@ -93,7 +95,7 @@ public class AutoFillInterceptor implements Interceptor { break; // 更新人 case MyBatisConstants.FIELD_UPDATE_BY: - setProperty(arg, MyBatisConstants.FIELD_UPDATE_BY, "测试"); + setProperty(arg, MyBatisConstants.FIELD_UPDATE_BY, userModel.getId()); break; // 更新日期 case MyBatisConstants.FIELD_UPDATE_TIME: @@ -123,6 +125,7 @@ public class AutoFillInterceptor implements Interceptor { * @param arg */ public void updateFill(Object arg) { + UserModel userModel = UserUtil.getUser(); // 2020-09-19 // 修改这儿 有可能会拿到一个 MapperMethod,需要特殊处理 Field[] fields; @@ -145,7 +148,7 @@ public class AutoFillInterceptor implements Interceptor { switch (f.getName()) { // 更新人 case MyBatisConstants.FIELD_UPDATE_BY: - setProperty(arg, MyBatisConstants.FIELD_UPDATE_BY, "测试修改"); + setProperty(arg, MyBatisConstants.FIELD_UPDATE_BY, userModel.getId()); break; // 更新日期 case MyBatisConstants.FIELD_UPDATE_TIME: diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/filter/TokenFilter.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/filter/TokenFilter.java new file mode 100644 index 0000000..20e346c --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/filter/TokenFilter.java @@ -0,0 +1,38 @@ +package org.opsli.core.filter; + +import org.apache.commons.lang3.StringUtils; +import org.opsli.common.api.TokenThreadLocal; +import org.opsli.core.utils.UserTokenUtil; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Token 拦截器 用于存放 token + */ +public class TokenFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + // 获得请求中 Token + String requestToken = UserTokenUtil.getRequestToken(request); + if(StringUtils.isNotEmpty(requestToken)){ + // 放入当前线程缓存中 + TokenThreadLocal.put(requestToken); + } + + filterChain.doFilter(servletRequest,servletResponse); + } + + @Override + public void destroy() { + // 线程销毁时 删除 token + TokenThreadLocal.remove(); + } +} \ No newline at end of file diff --git a/opsli-starter/src/main/java/org/opsli/general/StartPrint.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/general/StartPrint.java similarity index 67% rename from opsli-starter/src/main/java/org/opsli/general/StartPrint.java rename to opsli-base-support/opsli-core/src/main/java/org/opsli/core/general/StartPrint.java index 3bcd419..8975cf8 100644 --- a/opsli-starter/src/main/java/org/opsli/general/StartPrint.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/general/StartPrint.java @@ -1,4 +1,4 @@ -package org.opsli.general; +package org.opsli.core.general; import cn.hutool.core.lang.Console; import cn.hutool.core.thread.ThreadUtil; @@ -23,9 +23,10 @@ public enum StartPrint { INSTANCE; /** + * 成功 * 打印启动日志 */ - public void print(Environment env){ + public void successPrint(Environment env){ // 睡一秒打印 ThreadUtil.sleep(1, TimeUnit.SECONDS); String ip = "localhost"; @@ -38,11 +39,25 @@ public enum StartPrint { String contextPath = env.getProperty("server.servlet.context-path"); StringBuilder printStr = new StringBuilder(); printStr.append("\n----------------------------------------------------------\n") - .append("Opsli-Boot 框架已启动! 相关URLs:\n") + .append("Opsli-Boot 框架启动成功! 相关URLs:\n") .append("项目地址: \t\thttp://" + ip + ":" + serverPort + contextPath + "/\n") .append("Doc文档: \t\thttp://" + ip + ":" + serverPort + contextPath + "/doc.html\n") .append("----------------------------------------------------------\n"); Console.log(printStr.toString()); } + /** + * 势必啊 + * 打印启动日志 + */ + public void errorPrint(){ + // 睡一秒打印 + ThreadUtil.sleep(1, TimeUnit.SECONDS); + StringBuilder printStr = new StringBuilder(); + printStr.append("\n----------------------------------------------------------\n") + .append("Opsli-Boot 框架启动失败! 请检查相关配置!\n") + .append("----------------------------------------------------------\n"); + Console.log(printStr.toString()); + } + } diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/listener/ApplicationFailedEventListener.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/listener/ApplicationFailedEventListener.java new file mode 100644 index 0000000..53a4745 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/listener/ApplicationFailedEventListener.java @@ -0,0 +1,22 @@ +package org.opsli.core.listener; + +import lombok.extern.slf4j.Slf4j; +import org.opsli.core.general.StartPrint; +import org.springframework.boot.context.event.ApplicationFailedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * 系统启动失败 + * + * @author parker + * @date 2020-03-31 13:56 + */ +@Component +@Slf4j +public class ApplicationFailedEventListener implements ApplicationListener { + @Override + public void onApplicationEvent(ApplicationFailedEvent event) { + StartPrint.INSTANCE.errorPrint(); + } +} \ No newline at end of file diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/listener/ApplicationReadyEventListene.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/listener/ApplicationReadyEventListene.java new file mode 100644 index 0000000..488c04c --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/listener/ApplicationReadyEventListene.java @@ -0,0 +1,24 @@ +package org.opsli.core.listener; + +import lombok.extern.slf4j.Slf4j; +import org.opsli.core.general.StartPrint; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.stereotype.Component; + +/** + * 系统启动成功 + * + * @author parker + * @date 2020-03-31 13:56 + */ +@Component +@Slf4j +public class ApplicationReadyEventListene implements ApplicationListener { + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + ConfigurableApplicationContext applicationContext = event.getApplicationContext(); + StartPrint.INSTANCE.successPrint(applicationContext.getEnvironment()); + } +} \ No newline at end of file diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisCache.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisCache.java new file mode 100644 index 0000000..ec51a9f --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisCache.java @@ -0,0 +1,256 @@ +package org.opsli.core.security.shiro.cache; + + +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheException; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.util.CollectionUtils; +import org.opsli.core.security.shiro.exception.PrincipalIdNullException; +import org.opsli.core.security.shiro.exception.PrincipalInstanceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +/** + * @author: sunzhiqiang + * @date: 2018/6/22 + * @description: 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis + */ +public class RedisCache implements Cache { + + private static Logger logger = LoggerFactory.getLogger(RedisCache.class); + + private RedisManager redisManager; + private String keyPrefix = ""; + private int expire = 0; + private String principalIdFieldName = RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME; + + /** + * Construction + * @param redisManager + */ + public RedisCache(RedisManager redisManager, String prefix, int expire, String principalIdFieldName) { + if (redisManager == null) { + throw new IllegalArgumentException("redisManager cannot be null."); + } + this.redisManager = redisManager; + if (prefix != null && !"".equals(prefix)) { + this.keyPrefix = prefix; + } + if (expire != -1) { + this.expire = expire; + } + if (principalIdFieldName != null && !"".equals(principalIdFieldName)) { + this.principalIdFieldName = principalIdFieldName; + } + } + + @Override + public V get(K key) throws CacheException { + logger.debug("get key [{}]",key); + + if (key == null) { + return null; + } + + try { + String redisCacheKey = getRedisCacheKey(key); + Object rawValue = redisManager.get(redisCacheKey); + if (rawValue == null) { + return null; + } + V value = (V) rawValue; + return value; + } catch (Exception e) { + throw new CacheException(e); + } + } + + @Override + public V put(K key, V value) throws CacheException { + logger.debug("put key [{}]",key); + if (key == null) { + logger.warn("Saving a null key is meaningless, return value directly without call Redis."); + return value; + } + try { + String redisCacheKey = getRedisCacheKey(key); + redisManager.set(redisCacheKey, value != null ? value : null, expire); + return value; + } catch (Exception e) { + throw new CacheException(e); + } + } + + @Override + public V remove(K key) throws CacheException { + logger.debug("remove key [{}]",key); + if (key == null) { + return null; + } + try { + String redisCacheKey = getRedisCacheKey(key); + Object rawValue = redisManager.get(redisCacheKey); + V previous = (V) rawValue; + redisManager.del(redisCacheKey); + return previous; + } catch (Exception e) { + throw new CacheException(e); + } + } + + private String getRedisCacheKey(K key) { + if (key == null) { + return null; + } + return this.keyPrefix + getStringRedisKey(key); + } + + private String getStringRedisKey(K key) { + String redisKey; + if (key instanceof PrincipalCollection) { + redisKey = getRedisKeyFromPrincipalIdField((PrincipalCollection) key); + } else { + redisKey = key.toString(); + } + return redisKey; + } + + private String getRedisKeyFromPrincipalIdField(PrincipalCollection key) { + String redisKey; + Object principalObject = key.getPrimaryPrincipal(); + Method pincipalIdGetter = null; + Method[] methods = principalObject.getClass().getDeclaredMethods(); + for (Method m:methods) { + if (RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME.equals(this.principalIdFieldName) + && ("getAuthCacheKey".equals(m.getName()) || "getId".equals(m.getName()))) { + pincipalIdGetter = m; + break; + } + if (m.getName().equals("get" + this.principalIdFieldName.substring(0, 1).toUpperCase() + this.principalIdFieldName.substring(1))) { + pincipalIdGetter = m; + break; + } + } + if (pincipalIdGetter == null) { + throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName); + } + + try { + Object idObj = pincipalIdGetter.invoke(principalObject); + if (idObj == null) { + throw new PrincipalIdNullException(principalObject.getClass(), this.principalIdFieldName); + } + redisKey = idObj.toString(); + } catch (IllegalAccessException e) { + throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e); + } catch (InvocationTargetException e) { + throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e); + } + + return redisKey; + } + + + @Override + public void clear() throws CacheException { + logger.debug("clear cache"); + Set keys = null; + try { + keys = redisManager.scan(this.keyPrefix + "*"); + } catch (Exception e) { + logger.error("get keys error", e); + } + if (keys == null || keys.size() == 0) { + return; + } + for (String key: keys) { + redisManager.del(key); + } + } + + @Override + public int size() { + Long longSize = 0L; + try { + longSize = new Long(redisManager.scanSize(this.keyPrefix + "*")); + } catch (Exception e) { + logger.error("get keys error", e); + } + return longSize.intValue(); + } + + @SuppressWarnings("unchecked") + @Override + public Set keys() { + Set keys = null; + try { + keys = redisManager.scan(this.keyPrefix + "*"); + } catch (Exception e) { + logger.error("get keys error", e); + return Collections.emptySet(); + } + + if (CollectionUtils.isEmpty(keys)) { + return Collections.emptySet(); + } + + Set convertedKeys = new HashSet(); + for (String key:keys) { + try { + convertedKeys.add((K) key); + } catch (Exception e) { + logger.error("deserialize keys error", e); + } + } + return convertedKeys; + } + + @Override + public Collection values() { + Set keys = null; + try { + keys = redisManager.scan(this.keyPrefix + "*"); + } catch (Exception e) { + logger.error("get values error", e); + return Collections.emptySet(); + } + + if (CollectionUtils.isEmpty(keys)) { + return Collections.emptySet(); + } + + List values = new ArrayList(keys.size()); + for (String key : keys) { + V value = null; + try { + value = (V) redisManager.get(key); + } catch (Exception e) { + logger.error("deserialize values= error", e); + } + if (value != null) { + values.add(value); + } + } + return Collections.unmodifiableList(values); + } + + public String getKeyPrefix() { + return keyPrefix; + } + + public void setKeyPrefix(String keyPrefix) { + this.keyPrefix = keyPrefix; + } + + public String getPrincipalIdFieldName() { + return principalIdFieldName; + } + + public void setPrincipalIdFieldName(String principalIdFieldName) { + this.principalIdFieldName = principalIdFieldName; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisCacheManager.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisCacheManager.java new file mode 100644 index 0000000..d77c149 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisCacheManager.java @@ -0,0 +1,85 @@ +package org.opsli.core.security.shiro.cache; + +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheException; +import org.apache.shiro.cache.CacheManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @description: 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis + */ +public class RedisCacheManager implements CacheManager { + + private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class); + + /** + * fast lookup by name map + */ + private final ConcurrentMap caches = new ConcurrentHashMap(); + + private RedisManager redisManager; + + /** + * expire time in seconds + */ + private static final int DEFAULT_EXPIRE = 1800; + private int expire = DEFAULT_EXPIRE; + + /** + * The Redis key prefix for caches + */ + public static final String DEFAULT_CACHE_KEY_PREFIX = "shiro:cache:"; + private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX; + + public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME = "authCacheKey or id"; + private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME; + + @Override + public Cache getCache(String name) throws CacheException { + logger.debug("get cache, name={}",name); + + Cache cache = caches.get(name); + + if (cache == null) { + cache = new RedisCache(redisManager,keyPrefix + name + ":", expire, principalIdFieldName); + caches.put(name, cache); + } + return cache; + } + + public RedisManager getRedisManager() { + return redisManager; + } + + public void setRedisManager(RedisManager redisManager) { + this.redisManager = redisManager; + } + + public String getKeyPrefix() { + return keyPrefix; + } + + public void setKeyPrefix(String keyPrefix) { + this.keyPrefix = keyPrefix; + } + + public int getExpire() { + return expire; + } + + public void setExpire(int expire) { + this.expire = expire; + } + + public String getPrincipalIdFieldName() { + return principalIdFieldName; + } + + public void setPrincipalIdFieldName(String principalIdFieldName) { + this.principalIdFieldName = principalIdFieldName; + } +} \ No newline at end of file diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisManager.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisManager.java new file mode 100644 index 0000000..17f4dce --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/RedisManager.java @@ -0,0 +1,147 @@ +package org.opsli.core.security.shiro.cache; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; +import org.springframework.util.CollectionUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * + * @author sunzhiqiang + * 基于spring和redis的redisTemplate工具类 + */ +public class RedisManager { + + @Autowired + private RedisTemplate redisTemplate; + + //=============================common============================ + /** + * 指定缓存失效时间 + * @param key 键 + * @param time 时间(秒) + */ + public void expire(String key,long time){ + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + + /** + * 判断key是否存在 + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key){ + return redisTemplate.hasKey(key); + } + + /** + * 删除缓存 + * @param key 可以传一个值 或多个 + */ + @SuppressWarnings("unchecked") + public void del(String ... key){ + if(key!=null&&key.length>0){ + if(key.length==1){ + redisTemplate.delete(key[0]); + }else{ + redisTemplate.delete(CollectionUtils.arrayToList(key)); + } + } + } + + /** + * 批量删除key + * @param keys + */ + public void del(Collection keys){ + redisTemplate.delete(keys); + } + + //============================String============================= + /** + * 普通缓存获取 + * @param key 键 + * @return 值 + */ + public Object get(String key){ + return redisTemplate.opsForValue().get(key); + } + + /** + * 普通缓存放入 + * @param key 键 + * @param value 值 + */ + public void set(String key,Object value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 普通缓存放入并设置时间 + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + */ + public void set(String key,Object value,long time){ + if(time>0){ + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + }else{ + set(key, value); + } + } + + /** + * 使用scan命令 查询某些前缀的key + * @param key + * @return + */ + public Set scan(String key){ + Set execute = this.redisTemplate.execute(new RedisCallback>() { + + @Override + public Set doInRedis(RedisConnection connection) throws DataAccessException { + + Set binaryKeys = new HashSet<>(); + + Cursor cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key).count(1000).build()); + while (cursor.hasNext()) { + binaryKeys.add(new String(cursor.next())); + } + return binaryKeys; + } + }); + return execute; + } + + /** + * 使用scan命令 查询某些前缀的key 有多少个 + * 用来获取当前session数量,也就是在线用户 + * @param key + * @return + */ + public Long scanSize(String key){ + long dbSize = this.redisTemplate.execute(new RedisCallback() { + + @Override + public Long doInRedis(RedisConnection connection) throws DataAccessException { + long count = 0L; + Cursor cursor = connection.scan(ScanOptions.scanOptions().match(key).count(1000).build()); + while (cursor.hasNext()) { + cursor.next(); + count++; + } + return count; + } + }); + return dbSize; + } +} \ No newline at end of file diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/serializer/SerializeUtils.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/serializer/SerializeUtils.java new file mode 100644 index 0000000..1f51de3 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/cache/serializer/SerializeUtils.java @@ -0,0 +1,83 @@ +package org.opsli.core.security.shiro.cache.serializer; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import java.io.*; + +/** + *

序列化工具类

+ * + * @author sunzhiqiang23 + * @date 2020-04-27 19:48 + */ +public class SerializeUtils implements RedisSerializer { + + private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class); + + public static boolean isEmpty(byte[] data) { + return (data == null || data.length == 0); + } + + /** + * 序列化 + * @param object + * @return + * @throws SerializationException + */ + @Override + public byte[] serialize(Object object) throws SerializationException { + byte[] result = null; + + if (object == null) { + return new byte[0]; + } + try ( + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream) + ){ + + if (!(object instanceof Serializable)) { + throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() + " requires a Serializable payload " + + "but received an object of type [" + object.getClass().getName() + "]"); + } + + objectOutputStream.writeObject(object); + objectOutputStream.flush(); + result = byteStream.toByteArray(); + } catch (Exception ex) { + logger.error("Failed to serialize",ex); + } + return result; + } + + /** + * 反序列化 + * @param bytes + * @return + * @throws SerializationException + */ + @Override + public Object deserialize(byte[] bytes) throws SerializationException { + + Object result = null; + + if (isEmpty(bytes)) { + return null; + } + + try ( + ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes); + ObjectInputStream objectInputStream = new ObjectInputStream(byteStream) + ){ + result = objectInputStream.readObject(); + } catch (Exception e) { + logger.error("Failed to deserialize",e); + } + return result; + } + +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/exception/PrincipalIdNullException.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/exception/PrincipalIdNullException.java new file mode 100644 index 0000000..2459e80 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/exception/PrincipalIdNullException.java @@ -0,0 +1,10 @@ +package org.opsli.core.security.shiro.exception; + +public class PrincipalIdNullException extends RuntimeException { + + private static final String MESSAGE = "Principal Id shouldn't be null!"; + + public PrincipalIdNullException(Class clazz, String idMethodName) { + super(clazz + " id field: " + idMethodName + ", value is null\n" + MESSAGE); + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/exception/PrincipalInstanceException.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/exception/PrincipalInstanceException.java new file mode 100644 index 0000000..443c938 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/exception/PrincipalInstanceException.java @@ -0,0 +1,18 @@ +package org.opsli.core.security.shiro.exception; + +public class PrincipalInstanceException extends RuntimeException { + + private static final String MESSAGE = "We need a field to identify this Cache Object in Redis. " + + "So you need to defined an id field which you can get unique id to identify this principal. " + + "For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. " + + "For example, getUserId(), getUserName(), getEmail(), etc.\n" + + "Default value is \"id\", that means your principal object has a method called \"getId()\""; + + public PrincipalInstanceException(Class clazz, String idMethodName) { + super(clazz + " must has getter for field: " + idMethodName + "\n" + MESSAGE); + } + + public PrincipalInstanceException(Class clazz, String idMethodName, Exception e) { + super(clazz + " must has getter for field: " + idMethodName + "\n" + MESSAGE, e); + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/filter/OAuth2Filter.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/filter/OAuth2Filter.java new file mode 100644 index 0000000..64120e2 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/filter/OAuth2Filter.java @@ -0,0 +1,92 @@ +package org.opsli.core.security.shiro.filter; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.web.filter.authc.AuthenticatingFilter; +import org.opsli.api.base.result.ResultVo; +import org.opsli.core.security.shiro.token.OAuth2Token; +import org.opsli.core.utils.UserTokenUtil; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * oauth2过滤器 + * + * 就是因为,以前单机,页面的挑转自己能控制,失败的时候定一个错误码:401, + * + * 集中登录,集中授权, + * + * @author 孙志强 + + * @date 2017-05-20 13:00 + */ +public class OAuth2Filter extends AuthenticatingFilter { + + @Override + protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { + //获取请求token + String token = UserTokenUtil.getRequestToken((HttpServletRequest) request); + + if(StringUtils.isBlank(token)){ + return null; + } + + return new OAuth2Token(token); + } + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){ + return true; + } + // remeberMe ,remeberMe特殊页面,需要授权, + + return false; + } + + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + //获取请求token,如果token不存在,直接返回401 + String token = UserTokenUtil.getRequestToken((HttpServletRequest) request); + if(StringUtils.isBlank(token)){ + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); + httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin")); + httpResponse.setContentType("application/json; charset=utf-8"); + + ResultVo error = ResultVo.error(401, "令牌失效"); + httpResponse.getWriter().print(error.toJsonStr()); + return false; + } + + return executeLogin(request, response); + } + + @Override + protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setContentType("application/json;charset=utf-8"); + httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); + httpResponse.setHeader("Access-Control-Allow-Origin", httpServletRequest.getHeader("Origin")); + + try { + //处理登录失败的异常 + Throwable throwable = e.getCause() == null ? e : e.getCause(); + ResultVo error = ResultVo.error(401, throwable.getMessage()); + httpResponse.getWriter().print(error.toJsonStr()); + } catch (IOException e1) { + + } + return false; + } + + +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/realm/OAuth2Realm.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/realm/OAuth2Realm.java new file mode 100644 index 0000000..cf59b34 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/realm/OAuth2Realm.java @@ -0,0 +1,76 @@ +package org.opsli.core.security.shiro.realm; + +import org.apache.shiro.authc.*; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.opsli.api.wrapper.system.user.UserModel; +import org.opsli.core.security.shiro.token.OAuth2Token; +import org.opsli.core.utils.UserTokenUtil; +import org.opsli.core.utils.UserUtil; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 认证 + * + * @author 孙志强 + + * @date 2017-05-20 14:00 + */ +@Component +public class OAuth2Realm extends AuthorizingRealm { + + @Override + public boolean supports(AuthenticationToken token) { + return token instanceof OAuth2Token; + } + + /** + * 授权(验证权限时调用) + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + UserModel user = (UserModel) principals.getPrimaryPrincipal(); + String userId = user.getId(); + + //用户权限列表 + List permsSet = UserUtil.getUserAllPermsByUserId(userId); + + //用户角色列表 + List rolesSet = UserUtil.getUserRolesByUserId(userId); + + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + info.addStringPermissions(permsSet); + info.addRoles(rolesSet); + + return info; + } + + /** + * 认证(登录时调用) + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + String accessToken = (String) token.getPrincipal(); + + // 1. 校验 token 是否有效 + boolean verify = UserTokenUtil.verify(accessToken); + if(!verify){ + throw new IncorrectCredentialsException("token失效,请重新登录"); + } + + // 2. 查询 用户信息 + String userId = UserTokenUtil.getUserIdByToken(accessToken); + UserModel user = UserUtil.getUser(userId); + + // 3. 校验账户是否锁定 + if(user == null || user.getLocked().equals('1')){ + throw new LockedAccountException("账号已被锁定,请联系管理员"); + } + + return new SimpleAuthenticationInfo(user, accessToken, getName()); + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/MySessionListener.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/MySessionListener.java new file mode 100644 index 0000000..367a3ea --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/MySessionListener.java @@ -0,0 +1,29 @@ +package org.opsli.core.security.shiro.session; + +import org.apache.shiro.session.Session; +import org.apache.shiro.session.SessionListener; + +/** + *

+ * + * @author sunzhiqiang23 + * @date 2020-04-25 17:29 + */ +public class MySessionListener implements SessionListener { + + @Override + public void onStart(Session session) { + System.out.println("会话创建:" + session.getId()); + } + + @Override + public void onStop(Session session) { + System.out.println("会话退出:" + session.getId()); + } + + @Override + public void onExpiration(Session session) { + System.out.println("会话过期:" + session.getId()); + + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/SessionInMemory.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/SessionInMemory.java new file mode 100644 index 0000000..55c344d --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/SessionInMemory.java @@ -0,0 +1,32 @@ +package org.opsli.core.security.shiro.session; + +import org.apache.shiro.session.Session; + +import java.util.Date; + +/** + *

+ * + * @author sunzhiqiang23 + * @date 2020-04-27 20:52 + */ +public class SessionInMemory { + private Session session; + private Date createTime; + + public Session getSession() { + return session; + } + + public void setSession(Session session) { + this.session = session; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/ShiroSession.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/ShiroSession.java new file mode 100644 index 0000000..9aed8a5 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/ShiroSession.java @@ -0,0 +1,131 @@ +package org.opsli.core.security.shiro.session; + +import org.apache.shiro.session.mgt.SimpleSession; + +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * + * @author sunzhiqiang23 + * @date 2020-04-27 20:38 + */ + +import java.util.Map; + +/** + * @author: sunzhiqiang + * @date: 2020-04-27 20:38 + * @description: 由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法, + * 增加标识位,如果只是更新lastAccessTime SessionDao update方法直接返回 + */ +public class ShiroSession extends SimpleSession implements Serializable { + // 除lastAccessTime以外其他字段发生改变时为true + private boolean isChanged = false; + + public ShiroSession() { + super(); + this.setChanged(true); + } + + public ShiroSession(String host) { + super(host); + this.setChanged(true); + } + + + @Override + public void setId(Serializable id) { + super.setId(id); + this.setChanged(true); + } + + @Override + public void setStopTimestamp(Date stopTimestamp) { + super.setStopTimestamp(stopTimestamp); + this.setChanged(true); + } + + @Override + public void setExpired(boolean expired) { + super.setExpired(expired); + this.setChanged(true); + } + + @Override + public void setTimeout(long timeout) { + super.setTimeout(timeout); + this.setChanged(true); + } + + @Override + public void setHost(String host) { + super.setHost(host); + this.setChanged(true); + } + + @Override + public void setAttributes(Map attributes) { + super.setAttributes(attributes); + this.setChanged(true); + } + + @Override + public void setAttribute(Object key, Object value) { + super.setAttribute(key, value); + this.setChanged(true); + } + + @Override + public Object removeAttribute(Object key) { + this.setChanged(true); + return super.removeAttribute(key); + } + + /** + * 停止 + */ + @Override + public void stop() { + super.stop(); + this.setChanged(true); + } + + /** + * 设置过期 + */ + @Override + protected void expire() { + this.stop(); + this.setExpired(true); + } + + public boolean isChanged() { + return isChanged; + } + + public void setChanged(boolean isChanged) { + this.isChanged = isChanged; + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + protected boolean onEquals(SimpleSession ss) { + return super.onEquals(ss); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return super.toString(); + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/ShiroSessionManager.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/ShiroSessionManager.java new file mode 100644 index 0000000..5bf9663 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/session/ShiroSessionManager.java @@ -0,0 +1,53 @@ +package org.opsli.core.security.shiro.session; + + +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.UnknownSessionException; +import org.apache.shiro.session.mgt.SessionKey; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.apache.shiro.web.session.mgt.WebSessionKey; + +import javax.servlet.ServletRequest; +import java.io.Serializable; + +/** + *

解决单次请求需要多次访问redis

+ * + * @author sunzhiqiang23 + * @date 2020-04-27 20:58 + */ +@Slf4j +public class ShiroSessionManager extends DefaultWebSessionManager { + + /** + * 获取session + * 优化单次请求需要多次访问redis的问题 + * @param sessionKey + * @return + * @throws UnknownSessionException + */ + @Override + protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { + Serializable sessionId = getSessionId(sessionKey); + + ServletRequest request = null; + if (sessionKey instanceof WebSessionKey) { + request = ((WebSessionKey) sessionKey).getServletRequest(); + } + + if (request != null && null != sessionId) { + Object sessionObj = request.getAttribute(sessionId.toString()); + if (sessionObj != null) { + log.debug("read session from request"); + return (Session) sessionObj; + } + } + + Session session = super.retrieveSession(sessionKey); + if (request != null && null != sessionId) { + request.setAttribute(sessionId.toString(), session); + } + return session; + } +} \ No newline at end of file diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/token/JwtToken.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/token/JwtToken.java new file mode 100644 index 0000000..4488530 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/token/JwtToken.java @@ -0,0 +1,29 @@ +package org.opsli.core.security.shiro.token; + + +import org.apache.shiro.authc.AuthenticationToken; + +/** + * jwt token + * + * @author parker + + * @date 2017-05-20 13:22 + */ +public class JwtToken implements AuthenticationToken { + private String token; + + public JwtToken(String token){ + this.token = token; + } + + @Override + public String getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/token/OAuth2Token.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/token/OAuth2Token.java new file mode 100644 index 0000000..061ae7e --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/token/OAuth2Token.java @@ -0,0 +1,29 @@ +package org.opsli.core.security.shiro.token; + + +import org.apache.shiro.authc.AuthenticationToken; + +/** + * OAuth2 token + * + * @author 孙志强 + + * @date 2017-05-20 13:22 + */ +public class OAuth2Token implements AuthenticationToken { + private String token; + + public OAuth2Token(String token){ + this.token = token; + } + + @Override + public String getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/utils/RedisKeys.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/utils/RedisKeys.java new file mode 100644 index 0000000..30b7cfe --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/utils/RedisKeys.java @@ -0,0 +1,34 @@ +/** + * Copyright 2018 人人开源 http://www.renren.io + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.opsli.core.security.shiro.utils; + +/** + * Redis所有Keys + * + * @author Mark sunlightcs@gmail.com + * @since 3.0.0 2017-07-18 + */ +public class RedisKeys { + + public static String getSysConfigKey(String key){ + return "system:config:" + key; + } + + public static String getShiroSessionKey(String key){ + return "sessionid:" + key; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/utils/ShiroUtils.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/utils/ShiroUtils.java new file mode 100644 index 0000000..5295926 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/security/shiro/utils/ShiroUtils.java @@ -0,0 +1,84 @@ +/** + * Copyright 2018 人人开源 http://www.renren.io + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.opsli.core.security.shiro.utils; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.crypto.hash.SimpleHash; +import org.apache.shiro.session.Session; +import org.apache.shiro.subject.Subject; +import org.opsli.api.wrapper.system.user.UserModel; + +/** + * Shiro工具类 + * + * @author 孙志强 + + * @date 2016年11月12日 上午9:49:19 + */ +public class ShiroUtils { + + /** 加密算法 */ + public final static String hashAlgorithmName = "MD5"; + /** 循环次数 */ + public final static int hashIterations = 1; + + public static String sha256(String password, String salt) { + return new SimpleHash(hashAlgorithmName, password, salt, hashIterations).toString(); + } + + public static Session getSession() { + return SecurityUtils.getSubject().getSession(); + } + + public static Subject getSubject() { + return SecurityUtils.getSubject(); + } + + public static UserModel getUser() { + return (UserModel) SecurityUtils.getSubject().getPrincipal(); + } + + public static String getUserId() { + return getUser().getId(); + } + + public static void setSessionAttribute(Object key, Object value) { + getSession().setAttribute(key, value); + } + + public static Object getSessionAttribute(Object key) { + return getSession().getAttribute(key); + } + + public static boolean isLogin() { + return SecurityUtils.getSubject().getPrincipal() != null; + } + + public static void logout() { + SecurityUtils.getSubject().logout(); + } + + public static String getKaptcha(String key) throws RuntimeException { + Object kaptcha = getSessionAttribute(key); + if(kaptcha == null){ + throw new RuntimeException("验证码已失效"); + } + getSession().removeAttribute(key); + return kaptcha.toString(); + } + +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/Base64ConvertUtil.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/Base64ConvertUtil.java new file mode 100644 index 0000000..5c33af3 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/Base64ConvertUtil.java @@ -0,0 +1,34 @@ +package org.opsli.core.utils; + +import java.io.UnsupportedEncodingException; +import java.util.Base64; + +/** + * Base64工具 + * @author 孙志强 + * @date 2018/11/05 23:10 + */ +public final class Base64ConvertUtil { + /** + * 加密 + * @param str + * @return java.lang.String + */ + public static String encode(String str) throws UnsupportedEncodingException { + byte[] encodeBytes = Base64.getEncoder().encode(str.getBytes("utf-8")); + return new String(encodeBytes); + } + + /** + * 解密 + * @param str + * @return java.lang.String + */ + public static String decode(String str) throws UnsupportedEncodingException { + byte[] decodeBytes = Base64.getDecoder().decode(str.getBytes("utf-8")); + return new String(decodeBytes); + } + + // ============== + private Base64ConvertUtil(){} +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/CaptchaUtil.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/CaptchaUtil.java new file mode 100644 index 0000000..89f07b2 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/CaptchaUtil.java @@ -0,0 +1,88 @@ +package org.opsli.core.utils; + +import com.alibaba.fastjson.JSONObject; +import com.google.code.kaptcha.Producer; +import org.apache.commons.lang3.StringUtils; +import org.opsli.plugins.redis.RedisPlugin; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.awt.image.BufferedImage; + +/** + * 验证码 + * + * @author parker + * @since 2.0.0 2018-02-10 + */ +@Component +public class CaptchaUtil{ + + /** 缓存前缀 */ + private static final String PREFIX = "opsli:temp:captcha:"; + /** 默认验证码保存 5 分钟 */ + private static final int TIME_OUT = 300; + /** Redis插件 */ + private static RedisPlugin redisPlugin; + /** 谷歌验证码 */ + private static Producer producer; + + /** + * 获得验证码 + * @param uuid + * @return + */ + public static BufferedImage getCaptcha(String uuid) { + if(StringUtils.isBlank(uuid)){ + throw new RuntimeException("uuid不能为空"); + } + //生成文字验证码 + String code = producer.createText(); + + boolean ret = redisPlugin.put(PREFIX + uuid, code, TIME_OUT); + + if(ret){ + return producer.createImage(code); + } + return null; + } + + /** + * 校验验证码 + * @param uuid + * @param code + * @return + */ + public static boolean validate(String uuid, String code) { + if(StringUtils.isEmpty(uuid)) return false; + + // 验证码 + String codeTemp = (String) redisPlugin.get(PREFIX + uuid); + + if(StringUtils.isEmpty(codeTemp)){ + return false; + } + + //删除验证码 + redisPlugin.del(PREFIX + uuid); + + return codeTemp.equalsIgnoreCase(code); + } + + + + + + + // ========================== + + @Autowired + public void setRedisPlugin(RedisPlugin redisPlugin) { + CaptchaUtil.redisPlugin = redisPlugin; + } + + @Autowired + public void setProducer(Producer producer) { + CaptchaUtil.producer = producer; + } +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/DictUtil.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/DictUtil.java index ad6f7f2..972376a 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/DictUtil.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/DictUtil.java @@ -6,8 +6,8 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.opsli.api.base.result.ResultVo; import org.opsli.api.web.system.dict.DictDetailApi; -import org.opsli.api.wrapper.system.dict.DictModel; -import org.opsli.api.wrapper.system.dict.SysDictDetailModel; +import org.opsli.api.wrapper.system.dict.DictWrapper; +import org.opsli.api.wrapper.system.dict.DictDetailModel; import org.opsli.common.constants.CacheConstants; import org.opsli.common.constants.DictConstants; import org.opsli.core.cache.local.CacheUtil; @@ -50,29 +50,31 @@ public class DictUtil { * @return */ public static String getDictNameByValue(String typeCode, String dictValue, String defaultVal){ - try { - String dictName = ""; - SysDictDetailModel cacheModel = CacheUtil.getHash(DictConstants.CACHE_PREFIX_VALUE + typeCode, - dictValue, SysDictDetailModel.class); - if (cacheModel != null){ - dictName = cacheModel.getDictName(); - } - if (StringUtils.isNotEmpty(dictName)) return dictName; + String dictName = ""; + DictDetailModel cacheModel = CacheUtil.getHash(DictConstants.CACHE_PREFIX_VALUE + typeCode, + dictValue, DictDetailModel.class); + if (cacheModel != null){ + dictName = cacheModel.getDictName(); + } + if (StringUtils.isNotEmpty(dictName)) return dictName; - // 防止缓存穿透判断 - boolean hasNilFlag = CacheUtil.hasNilFlag("dict:" + typeCode + ":" + dictValue); - if(hasNilFlag){ - return defaultVal; - } + + // 防止缓存穿透判断 + boolean hasNilFlag = CacheUtil.hasNilFlag("dict:" + typeCode + ":" + dictValue); + if(hasNilFlag){ + return defaultVal; + } - // 锁凭证 redisLock 贯穿全程 - RedisLock redisLock = new RedisLock(); - redisLock.setLockName("dictLock:" + typeCode + ":" + dictValue) - .setAcquireTimeOut(3000L) - .setLockTimeOut(5000L); + // 锁凭证 redisLock 贯穿全程 + RedisLock redisLock = new RedisLock(); + redisLock.setLockName("dictLock:" + typeCode + ":" + dictValue) + .setAcquireTimeOut(3000L) + .setLockTimeOut(5000L); + + try { // 这里增加分布式锁 防止缓存击穿 // ============ 尝试加锁 redisLock = redisLockPlugins.tryLock(redisLock); @@ -81,40 +83,42 @@ public class DictUtil { } // 查询数据库 并保存到缓存内 - ResultVo> resultVo = dictDetailApi.findListByTypeCode(typeCode); + ResultVo> resultVo = dictDetailApi.findListByTypeCode(typeCode); if(resultVo.isSuccess()){ - List sysDictDetailModels = resultVo.getData(); - for (SysDictDetailModel model : sysDictDetailModels) { + List dictDetailModels = resultVo.getData(); + for (DictDetailModel model : dictDetailModels) { if(model.getDictValue().equals(dictValue)){ // 名称 dictName = model.getDictName(); - DictModel dictModel = new DictModel(); - dictModel.setTypeCode(model.getTypeCode()); - dictModel.setDictName(model.getDictName()); - dictModel.setDictValue(model.getDictValue()); - dictModel.setModel(model); + DictWrapper dictWrapperModel = new DictWrapper(); + dictWrapperModel.setTypeCode(model.getTypeCode()); + dictWrapperModel.setDictName(model.getDictName()); + dictWrapperModel.setDictValue(model.getDictValue()); + dictWrapperModel.setModel(model); // 保存至缓存 - DictUtil.put(dictModel); + DictUtil.put(dictWrapperModel); break; } } } - // ============ 释放锁 - redisLockPlugins.unLock(redisLock); - redisLock = null; - // 如果名称还是 为空 则赋默认值 - if(StringUtils.isEmpty(dictName)){ - // 加入缓存防穿透 - // 设置空变量 用于防止穿透判断 - CacheUtil.putNilFlag("dict:" + typeCode + ":" + dictValue); - dictName = defaultVal; - } - return dictName; }catch (Exception e){ log.error(e.getMessage(),e); return defaultVal; + }finally { + // ============ 释放锁 + redisLockPlugins.unLock(redisLock); + redisLock = null; } + + // 如果名称还是 为空 则赋默认值 + if(StringUtils.isEmpty(dictName)){ + // 加入缓存防穿透 + // 设置空变量 用于防止穿透判断 + CacheUtil.putNilFlag("dict:" + typeCode + ":" + dictValue); + dictName = defaultVal; + } + return dictName; } /** @@ -125,26 +129,28 @@ public class DictUtil { * @return */ public static String getDictValueByName(String typeCode, String dictName, String defaultVal){ - try { - String dictValue = ""; - SysDictDetailModel cacheModel = CacheUtil.getHash(DictConstants.CACHE_PREFIX_NAME + typeCode, - dictName, SysDictDetailModel.class); - if (cacheModel != null){ - dictValue = cacheModel.getDictValue(); - } - if (StringUtils.isNotEmpty(dictValue)) return dictValue; - // 防止缓存穿透判断 - boolean hasNilFlag = CacheUtil.hasNilFlag("dict:" + typeCode + ":" + dictName); - if(hasNilFlag){ - return defaultVal; - } + String dictValue = ""; + DictDetailModel cacheModel = CacheUtil.getHash(DictConstants.CACHE_PREFIX_NAME + typeCode, + dictName, DictDetailModel.class); + if (cacheModel != null){ + dictValue = cacheModel.getDictValue(); + } + if (StringUtils.isNotEmpty(dictValue)) return dictValue; + + // 防止缓存穿透判断 + boolean hasNilFlag = CacheUtil.hasNilFlag("dict:" + typeCode + ":" + dictName); + if(hasNilFlag){ + return defaultVal; + } + + // 锁凭证 redisLock 贯穿全程 + RedisLock redisLock = new RedisLock(); + redisLock.setLockName("dictLock:" + typeCode + ":" + dictName) + .setAcquireTimeOut(3000L) + .setLockTimeOut(10000L); - // 锁凭证 redisLock 贯穿全程 - RedisLock redisLock = new RedisLock(); - redisLock.setLockName("dictLock:" + typeCode + ":" + dictName) - .setAcquireTimeOut(3000L) - .setLockTimeOut(10000L); + try { // 这里增加分布式锁 防止缓存击穿 // ============ 尝试加锁 redisLock = redisLockPlugins.tryLock(redisLock); @@ -153,40 +159,43 @@ public class DictUtil { } // 查询数据库 并保存到缓存内 - ResultVo> resultVo = dictDetailApi.findListByTypeCode(typeCode); + ResultVo> resultVo = dictDetailApi.findListByTypeCode(typeCode); if(resultVo.isSuccess()){ - List sysDictDetailModels = resultVo.getData(); - for (SysDictDetailModel model : sysDictDetailModels) { + List dictDetailModels = resultVo.getData(); + for (DictDetailModel model : dictDetailModels) { if(model.getDictName().equals(dictName)){ // 值 dictValue = model.getDictValue(); - DictModel dictModel = new DictModel(); - dictModel.setTypeCode(model.getTypeCode()); - dictModel.setDictName(model.getDictName()); - dictModel.setDictValue(model.getDictValue()); - dictModel.setModel(model); + DictWrapper dictWrapperModel = new DictWrapper(); + dictWrapperModel.setTypeCode(model.getTypeCode()); + dictWrapperModel.setDictName(model.getDictName()); + dictWrapperModel.setDictValue(model.getDictValue()); + dictWrapperModel.setModel(model); // 保存至缓存 - DictUtil.put(dictModel); + DictUtil.put(dictWrapperModel); break; } } } - // ============ 释放锁 - redisLockPlugins.unLock(redisLock); - redisLock = null; - // 如果值还是 为空 则赋默认值 - if(StringUtils.isEmpty(dictValue)){ - // 加入缓存防穿透 - // 设置空变量 用于防止穿透判断 - CacheUtil.putNilFlag("dict:" + typeCode + ":" + dictName); - dictValue = defaultVal; - } - return dictValue; }catch (Exception e){ log.error(e.getMessage(),e); return defaultVal; + }finally { + // ============ 释放锁 + redisLockPlugins.unLock(redisLock); + redisLock = null; + } + + + // 如果值还是 为空 则赋默认值 + if(StringUtils.isEmpty(dictValue)){ + // 加入缓存防穿透 + // 设置空变量 用于防止穿透判断 + CacheUtil.putNilFlag("dict:" + typeCode + ":" + dictName); + dictValue = defaultVal; } + return dictValue; } /** @@ -194,8 +203,8 @@ public class DictUtil { * @param typeCode * @return */ - public static List getDictList(String typeCode){ - List dictModels = Lists.newArrayList(); + public static List getDictList(String typeCode){ + List dictWrapperModels = Lists.newArrayList(); try { String key = CacheUtil.handleKey(CacheConstants.EDEN_HASH_DATA, DictConstants.CACHE_PREFIX_VALUE + typeCode); Map dictMap = redisPlugin.hGetAll(key); @@ -206,18 +215,18 @@ public class DictUtil { if(jsonObject == null){ continue; } - SysDictDetailModel model = jsonObject.toJavaObject(SysDictDetailModel.class); - DictModel dictModel = new DictModel(); - dictModel.setTypeCode(typeCode); - dictModel.setDictName(model.getDictName()); - dictModel.setDictValue(model.getDictValue()); - dictModels.add(dictModel); + DictDetailModel model = jsonObject.toJavaObject(DictDetailModel.class); + DictWrapper dictWrapperModel = new DictWrapper(); + dictWrapperModel.setTypeCode(typeCode); + dictWrapperModel.setDictName(model.getDictName()); + dictWrapperModel.setDictValue(model.getDictValue()); + dictWrapperModels.add(dictWrapperModel); } }catch (Exception e){ log.error(e.getMessage(),e); - dictModels = Lists.newArrayList(); + dictWrapperModels = Lists.newArrayList(); } - return dictModels; + return dictWrapperModels; } @@ -229,7 +238,7 @@ public class DictUtil { * @param model 字典模型 * @return */ - public static void put(DictModel model){ + public static void put(DictWrapper model){ CacheUtil.putEdenHash(DictConstants.CACHE_PREFIX_NAME + model.getTypeCode(), model.getDictName(), model.getModel()); CacheUtil.putEdenHash(DictConstants.CACHE_PREFIX_VALUE + model.getTypeCode(), @@ -244,7 +253,7 @@ public class DictUtil { * @param model 字典模型 * @return */ - public static void del(DictModel model){ + public static void del(DictWrapper model){ CacheUtil.delEdenHash(DictConstants.CACHE_PREFIX_NAME + model.getTypeCode(), model.getDictName()); CacheUtil.delEdenHash(DictConstants.CACHE_PREFIX_VALUE + model.getTypeCode(), model.getDictValue()); } @@ -255,9 +264,9 @@ public class DictUtil { * @return */ public static void delAll(String typeCode){ - List dictList = DictUtil.getDictList(typeCode); - for (DictModel dictModel : dictList) { - DictUtil.del(dictModel); + List dictWrapperList = DictUtil.getDictList(typeCode); + for (DictWrapper dictWrapperModel : dictWrapperList) { + DictUtil.del(dictWrapperModel); } } @@ -276,6 +285,6 @@ public class DictUtil { @Autowired public void setDictDetailApi(DictDetailApi dictDetailApi) { - DictUtil.dictDetailApi = dictDetailApi; + // DictUtil.dictDetailApi = dictDetailApi; } } diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/ExcelUtil.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/ExcelUtil.java index 71308a4..b7321e5 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/ExcelUtil.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/ExcelUtil.java @@ -5,7 +5,7 @@ import com.alibaba.excel.support.ExcelTypeEnum; import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.opsli.api.wrapper.system.dict.DictModel; +import org.opsli.api.wrapper.system.dict.DictWrapper; import org.opsli.common.enums.ExcelOperate; import org.opsli.plugins.excel.ExcelPlugin; import org.opsli.plugins.excel.annotation.ExcelInfo; @@ -75,7 +75,7 @@ public class ExcelUtil extends ExcelPlugin { // 字段名 - 字典code Map fieldAndTypeCode = Maps.newHashMap(); // 字典code - 字典值 - Map> typeCodeAndValue = null; + Map> typeCodeAndValue = null; Field[] fields = ReflectUtil.getFields(typeClazz); for (Field field : fields) { @@ -116,7 +116,7 @@ public class ExcelUtil extends ExcelPlugin { * @return */ private void handleDict(T data, ExcelOperate operate, Map fieldAndTypeCode, - Map> typeCodeAndValue + Map> typeCodeAndValue ){ // 如果没有字典 则直接退出 if(fieldAndTypeCode.size() == 0 || typeCodeAndValue == null || typeCodeAndValue.size() == 0){ @@ -129,9 +129,9 @@ public class ExcelUtil extends ExcelPlugin { String fieldName = entry.getKey(); String typeCode = entry.getValue(); String fieldValue = (String) ReflectUtil.getFieldValue(data, fieldName); - List dictModels = typeCodeAndValue.get(typeCode); + List dictWrapperModels = typeCodeAndValue.get(typeCode); // 匹配字典 - String dictVal = this.matchingDict(dictModels, fieldValue, operate); + String dictVal = this.matchingDict(dictWrapperModels, fieldValue, operate); if(StringUtils.isEmpty(dictVal)){ continue; } @@ -148,43 +148,43 @@ public class ExcelUtil extends ExcelPlugin { * @param fieldAndTypeCode * @return */ - public Map> getDictMap(Map fieldAndTypeCode){ + public Map> getDictMap(Map fieldAndTypeCode){ // 字典code - 字典值 - Map> typeCodeAndValue = Maps.newHashMap(); + Map> typeCodeAndValue = Maps.newHashMap(); // 取Redis 值 for (Map.Entry entry : fieldAndTypeCode.entrySet()) { String typeCode = entry.getValue(); - List dictList = DictUtil.getDictList(typeCode); + List dictWrapperList = DictUtil.getDictList(typeCode); // 如果字典 List 为空 则走下一个 - if(dictList == null || dictList.size() == 0) continue; - typeCodeAndValue.put(typeCode, dictList); + if(dictWrapperList == null || dictWrapperList.size() == 0) continue; + typeCodeAndValue.put(typeCode, dictWrapperList); } return typeCodeAndValue; } /** * 匹配字典数据 - * @param dictModels + * @param dictWrapperModels * @param fieldValue * @param operate * @return */ - private String matchingDict(List dictModels, String fieldValue, ExcelOperate operate){ + private String matchingDict(List dictWrapperModels, String fieldValue, ExcelOperate operate){ String val = ""; - for (DictModel dictModel : dictModels) { + for (DictWrapper dictWrapperModel : dictWrapperModels) { // 读操作 if(ExcelOperate.READ == operate){ // 判断 Excel 读入 字典名称是否与 当前字典匹配 - if(dictModel.getDictName().equals(fieldValue)){ - val = dictModel.getDictValue(); + if(dictWrapperModel.getDictName().equals(fieldValue)){ + val = dictWrapperModel.getDictValue(); break; } } // 写操作 else if(ExcelOperate.WRITE == operate){ // 判断 Excel 写出 字典值是否与 当前字典匹配 - if(dictModel.getDictValue().equals(fieldValue)){ - val = dictModel.getDictName(); + if(dictWrapperModel.getDictValue().equals(fieldValue)){ + val = dictWrapperModel.getDictName(); break; } } diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/JwtUtil.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/JwtUtil.java new file mode 100644 index 0000000..76e2560 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/JwtUtil.java @@ -0,0 +1,110 @@ +package org.opsli.core.utils; + +import cn.hutool.setting.dialect.Props; +import cn.hutool.setting.dialect.PropsUtil; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.DecodedJWT; +import lombok.extern.slf4j.Slf4j; +import org.opsli.common.constants.SignConstants; + +import java.io.UnsupportedEncodingException; +import java.util.Date; + +/** + * JWT工具类 + * @author sunzhiqiang23 + * @date 2020/05/29 21:23 + */ +@Slf4j +public final class JwtUtil { + + + /** + * 过期时间改为从配置文件获取 120 分钟 两小时 + */ + public static final long EXPIRE; + + /** + * JWT认证加密私钥(Base64加密) + */ + private static final String encryptJWTKey="a30ade6452725123436288ccae58570738ee"; + + static { + Props props = PropsUtil.get("application.yaml"); + // token 有效时间 + EXPIRE = props.getLong("opsli.token-effective-time", 120L) * 60 * 1000; + } + + + /** + * 校验验证帐号加JWT私钥解密 是否正确 + * @param token Token + * @return boolean 是否正确 + */ + public static boolean verify(String token) { + try { + String secret = getClaim(token, SignConstants.ACCOUNT) + Base64ConvertUtil.decode(encryptJWTKey); + Algorithm algorithm = Algorithm.HMAC256(secret); + JWTVerifier verifier = JWT.require(algorithm).build(); + verifier.verify(token); + return true; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("认证解密异常:" + e.getMessage()); + } + } + + /** + * 获得Token中的信息 + * @param token + * @param claim + * @return java.lang.String + */ + public static String getClaim(String token, String claim) { + try { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaim(claim).asString(); + } catch (JWTDecodeException e) { + throw new RuntimeException("解密异常:" + e.getMessage()); + } + } + + /** + * 生成签名 + * @param account 帐号 + * @return java.lang.String 返回加密的Token + */ + public static String sign(String account, String userId) { + try { + // 帐号加JWT私钥加密 + String secret = account + Base64ConvertUtil.decode(encryptJWTKey); + // 此处过期时间是以毫秒为单位,所以乘以1000 + Date date = new Date(System.currentTimeMillis() + EXPIRE); + Algorithm algorithm = Algorithm.HMAC256(secret); + // 附带account帐号信息 + return JWT.create() + .withClaim(SignConstants.ACCOUNT, account) + .withClaim(SignConstants.USER_ID, userId) + .withClaim(SignConstants.TIMESTAMP, String.valueOf(System.currentTimeMillis())) + // token 过期时间 + .withExpiresAt(date) + .sign(algorithm); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("加密异常:" + e.getMessage()); + } + } + + + public static void main(String[] args) { + String aaaaaa = JwtUtil.sign("aaaaaa","123123"); + + boolean verify = JwtUtil.verify(aaaaaa); + System.out.println(aaaaaa); + System.out.println(verify); + } + + // ================== + private JwtUtil(){} +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/TokenGenerator.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/TokenGenerator.java new file mode 100644 index 0000000..329c41a --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/TokenGenerator.java @@ -0,0 +1,57 @@ +package org.opsli.core.utils; + + +import java.security.MessageDigest; +import java.util.UUID; + +/** + * 生成token + * + * @author Parker + + * @date 2017-05-20 14:41 + */ +public final class TokenGenerator { + + public static String generateValue() { + return generateValue(UUID.randomUUID().toString()); + } + + private static final char[] hexCode = "0123456789abcdef".toCharArray(); + + + /** + * 生成Token + * @param param + * @return + */ + public static String generateValue(String param) { + try { + MessageDigest algorithm = MessageDigest.getInstance("MD5"); + algorithm.reset(); + algorithm.update(param.getBytes()); + byte[] messageDigest = algorithm.digest(); + return toHexString(messageDigest); + } catch (Exception e) { + throw new RuntimeException("生成Token失败", e); + } + } + + + + public static String toHexString(byte[] data) { + if(data == null) { + return null; + } + StringBuilder r = new StringBuilder(data.length*2); + for ( byte b : data) { + r.append(hexCode[(b >> 4) & 0xF]); + r.append(hexCode[(b & 0xF)]); + } + return r.toString(); + } + + + // ========================= + private TokenGenerator(){} +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/UserTokenUtil.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/UserTokenUtil.java new file mode 100644 index 0000000..e3fc441 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/UserTokenUtil.java @@ -0,0 +1,183 @@ +package org.opsli.core.utils; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.crypto.hash.Md5Hash; +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.wrapper.system.user.UserModel; +import org.opsli.common.constants.SignConstants; +import org.opsli.plugins.redis.RedisPlugin; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * User Token Util + * + * @author parker + */ +@Slf4j +@Component +public class UserTokenUtil { + + + /** 缓存前缀 */ + private static final String PREFIX = "opsli:ticket:"; + + /** Redis插件 */ + private static RedisPlugin redisPlugin; + + /** + * 根据 user 创建Token + * @param user + * @return + */ + public static ResultVo> createToken(UserModel user) { + if (user == null) { + return ResultVo.error("生成Token失败"); + } + + Map map = Maps.newHashMapWithExpectedSize(2); + try { + + // 生效时间 + int expire = Integer.parseInt( + String.valueOf(JwtUtil.EXPIRE) + ); + + // 生成 Token 包含 username userId timestamp + String signToken = JwtUtil.sign(user.getUsername(), user.getId()); + + // 生成MD5 16进制码 用于缩减存储 + String signTokenHex = new Md5Hash(signToken).toHex(); + + // 获得当前时间戳时间 + long timestamp = Long.parseLong(JwtUtil.getClaim(signToken, SignConstants.TIMESTAMP)); + DateTime currDate = DateUtil.date(timestamp); + + // 获得失效偏移量时间 + DateTime dateTime = DateUtil.offsetMillisecond(currDate, expire); + long endTimestamp = dateTime.getTime(); + + // token 缓存真实失效时间 建议大于 最终时间 -- 多加了20分钟的失效时间 + // 在redis存一份 token 是为了防止 认为造假 + boolean tokenFlag = redisPlugin.put(PREFIX + signTokenHex, endTimestamp, expire + 20); + if(tokenFlag){ + map.put("token", signToken); + map.put("expire", endTimestamp); + return ResultVo.success(map); + } + return ResultVo.error("生成Token失败"); + }catch (Exception e){ + log.error(e.getMessage() , e); + return ResultVo.error(e.getMessage()); + } + } + + /** + * 根据 Token 获得用户ID + * @param token + * @return + */ + public static String getUserIdByToken(String token) { + if(StringUtils.isEmpty(token)) return null; + String userId = ""; + try { + userId = JwtUtil.getClaim(token, SignConstants.USER_ID); + }catch (Exception e){} + return userId; + } + + /** + * 根据 Token 获得 username + * @param token + * @return + */ + public static String getUserNameByToken(String token) { + if(StringUtils.isEmpty(token)) return null; + String username = ""; + try { + username = JwtUtil.getClaim(token, SignConstants.ACCOUNT); + }catch (Exception e){} + return username; + } + + + + /** + * 退出登陆 + * @param token + */ + public static void logout(String token) { + if(StringUtils.isEmpty(token)) return; + try { + // 生成MD5 16进制码 用于缩减存储 + String signTokenHex = new Md5Hash(token).toHex(); + + redisPlugin.del(PREFIX + signTokenHex); + }catch (Exception e){} + } + + /** + * 验证 token + * @param token + */ + public static boolean verify(String token) { + if(StringUtils.isEmpty(token)) return false; + + try { + // 1. 校验是否是有效的 token + boolean tokenVerify = JwtUtil.verify(token); + if(!tokenVerify){ + return false; + } + + // 2. 校验当前缓存中token是否失效 + // 生成MD5 16进制码 用于缩减存储 + String signTokenHex = new Md5Hash(token).toHex(); + + Long endTimestamp = (Long) redisPlugin.get(PREFIX + signTokenHex); + if(endTimestamp == null){ + return false; + } + + // JWT 自带过期校验 无需多做处理 + + }catch (TokenExpiredException e){ + return false; + }catch (Exception e){ + return false; + } + return true; + } + + + // ========================== + + /** + * 获取请求的token + */ + public static String getRequestToken(HttpServletRequest httpRequest){ + //从header中获取token + String token = httpRequest.getHeader("token"); + + //如果header中不存在token,则从参数中获取token + if(StringUtils.isBlank(token)){ + token = httpRequest.getParameter("token"); + } + + return token; + } + + @Autowired + public void setRedisPlugin(RedisPlugin redisPlugin) { + UserTokenUtil.redisPlugin = redisPlugin; + } + +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/UserUtil.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/UserUtil.java index 176e17d..4c62aed 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/UserUtil.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/UserUtil.java @@ -1,5 +1,22 @@ package org.opsli.core.utils; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.web.system.user.UserApi; +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.api.wrapper.system.user.UserModel; +import org.opsli.common.api.TokenThreadLocal; +import org.opsli.core.cache.local.CacheUtil; +import org.opsli.plugins.redis.RedisLockPlugins; +import org.opsli.plugins.redis.lock.RedisLock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + /** * @BelongsProject: opsli-boot * @BelongsPackage: org.opsli.core.utils @@ -7,20 +24,483 @@ package org.opsli.core.utils; * @CreateTime: 2020-09-19 20:03 * @Description: 用户工具类 */ -public final class UserUtil { +@Slf4j +@Component +public class UserUtil { + + /** 超级管理员名称 */ + private static final String SUPER_ADMIN = "system"; + private static final String PREFIX_ID = "userId:"; + private static final String PREFIX_ID_ROLES = "userId:roles:"; + private static final String PREFIX_ID_PERMISSIONS = "userId:permissions:"; + private static final String PREFIX_ID_MENUS = "userId:menus:"; + private static final String PREFIX_USERNAME = "username:"; + + + + + /** Redis分布式锁 */ + private static RedisLockPlugins redisLockPlugins; + + /** 用户Service */ + private static UserApi userApi; + + + /** + * 获得当前系统登陆用户 + * @return + */ + public static UserModel getUser(){ + String token = TokenThreadLocal.get(); + + if(StringUtils.isEmpty(token)){ + return null; + } + + String userId = UserTokenUtil.getUserIdByToken(token); + UserModel user = getUser(userId); + if(user == null){ + throw new RuntimeException("Token 失效"); + } + return user; + } + + /** + * 根据ID 获得用户 + * @param userId + * @return + */ + public static UserModel getUser(String userId){ + // 先从缓存里拿 + UserModel userModel = CacheUtil.get(PREFIX_ID + userId, UserModel.class); + if (userModel != null){ + return userModel; + } + + // 拿不到 -------- + // 防止缓存穿透判断 + boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_ID + userId); + if(hasNilFlag){ + return null; + } + + // 锁凭证 redisLock 贯穿全程 + RedisLock redisLock = new RedisLock(); + redisLock.setLockName(PREFIX_ID + userId) + .setAcquireTimeOut(3000L) + .setLockTimeOut(5000L); + + try { + // 这里增加分布式锁 防止缓存击穿 + // ============ 尝试加锁 + redisLock = redisLockPlugins.tryLock(redisLock); + if(redisLock == null){ + return null; + } + + // 查询数据库 + UserModel userModelTemp = new UserModel(); + userModelTemp.setId(userId); + // 设置为系统内部调用 否则 会拿到 空值 + userModelTemp.setIzApi(true); + ResultVo resultVo = userApi.get(userModelTemp); + if(resultVo.isSuccess()){ + userModel = resultVo.getData(); + // 存入缓存 + CacheUtil.put(PREFIX_ID + userId, userModel); + } + }catch (Exception e){ + log.error(e.getMessage(),e); + }finally { + // ============ 释放锁 + redisLockPlugins.unLock(redisLock); + redisLock = null; + } + + if(userModel == null){ + // 设置空变量 用于防止穿透判断 + CacheUtil.putNilFlag(PREFIX_ID + userId); + return null; + } + + return userModel; + } - private UserUtil(){} + + /** + * 根据 userName 获得用户 + * @param userName + * @return + */ + public static UserModel getUserByUserName(String userName){ + // 先从缓存里拿 + UserModel userModel = CacheUtil.get(PREFIX_USERNAME + userName, UserModel.class); + if (userModel != null){ + return userModel; + } + + // 拿不到 -------- + // 防止缓存穿透判断 + boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_USERNAME + userName); + if(hasNilFlag){ + return null; + } + + // 锁凭证 redisLock 贯穿全程 + RedisLock redisLock = new RedisLock(); + redisLock.setLockName(PREFIX_USERNAME + userName) + .setAcquireTimeOut(3000L) + .setLockTimeOut(5000L); + + try { + // 这里增加分布式锁 防止缓存击穿 + // ============ 尝试加锁 + redisLock = redisLockPlugins.tryLock(redisLock); + if(redisLock == null){ + return null; + } + + // 查询数据库 + ResultVo resultVo = userApi.getUserByUsername(userName); + if(resultVo.isSuccess()){ + userModel = resultVo.getData(); + // 存入缓存 + CacheUtil.put(PREFIX_USERNAME + userName, userModel); + } + }catch (Exception e){ + log.error(e.getMessage(),e); + }finally { + // ============ 释放锁 + redisLockPlugins.unLock(redisLock); + redisLock = null; + } + + if(userModel == null){ + // 设置空变量 用于防止穿透判断 + CacheUtil.putNilFlag(PREFIX_USERNAME + userName); + return null; + } + + return userModel; + } + + /** + * 根据 userId 获得用户角色列表 + * @param userId + * @return + */ + public static List getUserRolesByUserId(String userId){ + List roles = null; + // 先从缓存里拿 + List rolesObj = CacheUtil.get(PREFIX_ID_ROLES + userId, List.class); + if (rolesObj != null && !rolesObj.isEmpty()){ + roles = Lists.newArrayListWithCapacity(rolesObj.size()); + for (Object role : rolesObj) { + roles.add((String) role); + } + return roles; + } + + // 拿不到 -------- + // 防止缓存穿透判断 + boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_ID_ROLES + userId); + if(hasNilFlag){ + return null; + } + + // 锁凭证 redisLock 贯穿全程 + RedisLock redisLock = new RedisLock(); + redisLock.setLockName(PREFIX_ID_ROLES + userId) + .setAcquireTimeOut(3000L) + .setLockTimeOut(5000L); + + try { + // 这里增加分布式锁 防止缓存击穿 + // ============ 尝试加锁 + redisLock = redisLockPlugins.tryLock(redisLock); + if(redisLock == null){ + return null; + } + + // 查询数据库 + ResultVo> resultVo = userApi.getRolesByUserId(userId); + if(resultVo.isSuccess()){ + roles = resultVo.getData(); + // 存入缓存 + CacheUtil.put(PREFIX_ID_ROLES + userId, roles); + } + }catch (Exception e){ + log.error(e.getMessage(),e); + }finally { + // ============ 释放锁 + redisLockPlugins.unLock(redisLock); + redisLock = null; + } + + if(roles == null){ + // 设置空变量 用于防止穿透判断 + CacheUtil.putNilFlag(PREFIX_ID_ROLES + userId); + return null; + } + + return roles; + } + + + /** + * 根据 userId 获得用户权限列表 + * @param userId + * @return + */ + public static List getUserAllPermsByUserId(String userId){ + List permissions = null; + // 先从缓存里拿 + List permissionsObj = CacheUtil.get(PREFIX_ID_PERMISSIONS + userId, List.class); + if (permissionsObj != null && !permissionsObj.isEmpty()){ + permissions = Lists.newArrayListWithCapacity(permissionsObj.size()); + for (Object permission : permissionsObj) { + permissions.add((String) permission); + } + return permissions; + } + + // 拿不到 -------- + // 防止缓存穿透判断 + boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_ID_PERMISSIONS + userId); + if(hasNilFlag){ + return null; + } + + // 锁凭证 redisLock 贯穿全程 + RedisLock redisLock = new RedisLock(); + redisLock.setLockName(PREFIX_ID_PERMISSIONS + userId) + .setAcquireTimeOut(3000L) + .setLockTimeOut(5000L); + + try { + // 这里增加分布式锁 防止缓存击穿 + // ============ 尝试加锁 + redisLock = redisLockPlugins.tryLock(redisLock); + if(redisLock == null){ + return null; + } + + // 查询数据库 + ResultVo> resultVo = userApi.getAllPerms(userId); + if(resultVo.isSuccess()){ + permissions = resultVo.getData(); + // 存入缓存 + CacheUtil.put(PREFIX_ID_PERMISSIONS + userId, permissions); + } + }catch (Exception e){ + log.error(e.getMessage(),e); + }finally { + // ============ 释放锁 + redisLockPlugins.unLock(redisLock); + redisLock = null; + } + + if(permissions == null){ + // 设置空变量 用于防止穿透判断 + CacheUtil.putNilFlag(PREFIX_ID_PERMISSIONS + userId); + return null; + } + + return permissions; + } + + /** + * 根据 userId 获得用户菜单 + * @param userId + * @return + */ + public static List getMenuListByUserId(String userId){ + List menus = null; + // 先从缓存里拿 + List menusObj = CacheUtil.get(PREFIX_ID_MENUS + userId, List.class); + if (menusObj != null && !menusObj.isEmpty()){ + menus = Lists.newArrayListWithCapacity(menusObj.size()); + for (Object menu : menusObj) { + JSONObject jsonObject = (JSONObject) menu; + if(jsonObject != null){ + menus.add(jsonObject.toJavaObject(MenuModel.class)); + } + } + return menus; + } + + // 拿不到 -------- + // 防止缓存穿透判断 + boolean hasNilFlag = CacheUtil.hasNilFlag(PREFIX_ID_MENUS + userId); + if(hasNilFlag){ + return null; + } + + // 锁凭证 redisLock 贯穿全程 + RedisLock redisLock = new RedisLock(); + redisLock.setLockName(PREFIX_ID_MENUS + userId) + .setAcquireTimeOut(3000L) + .setLockTimeOut(5000L); + + try { + // 这里增加分布式锁 防止缓存击穿 + // ============ 尝试加锁 + redisLock = redisLockPlugins.tryLock(redisLock); + if(redisLock == null){ + return null; + } + + // 查询数据库 + ResultVo> resultVo = userApi.getMenuListByUserId(userId); + if(resultVo.isSuccess()){ + menus = resultVo.getData(); + // 存入缓存 + CacheUtil.put(PREFIX_ID_MENUS + userId, menus); + } + }catch (Exception e){ + log.error(e.getMessage(),e); + }finally { + // ============ 释放锁 + redisLockPlugins.unLock(redisLock); + redisLock = null; + } + + if(menus == null){ + // 设置空变量 用于防止穿透判断 + CacheUtil.putNilFlag(PREFIX_ID_MENUS + userId); + return null; + } + + return menus; + } + + // ============== 刷新缓存 ============== + + /** + * 刷新用户 + * @param user + * @return + */ + public static void refreshUser(UserModel user){ + if(user == null || StringUtils.isEmpty(user.getId())){ + return; + } + + UserModel userModelById = CacheUtil.get(PREFIX_ID + user.getId(), UserModel.class); + UserModel userModelByUsername = CacheUtil.get(PREFIX_USERNAME + user.getUsername(), + UserModel.class); + + // 只要有一个不为空 则执行刷新 + if (userModelById != null || userModelByUsername != null){ + // 先删除 + CacheUtil.del(PREFIX_ID + user.getId()); + CacheUtil.del(PREFIX_USERNAME + user.getUsername()); + // 再赋值 + CacheUtil.put(PREFIX_ID + user.getId(), user); + CacheUtil.put(PREFIX_USERNAME + user.getUsername(), user); + // 清除空拦截 + CacheUtil.putNilFlag(PREFIX_ID + user.getId()); + CacheUtil.putNilFlag(PREFIX_USERNAME + user.getUsername()); + } + } + + + /** + * 刷新用户角色 + * @param userId + * @param roleCodes + * @return + */ + public static void refreshUserRoles(String userId, List roleCodes){ + if(roleCodes == null || roleCodes.isEmpty()){ + return; + } + + List list = CacheUtil.get(PREFIX_ID_ROLES + userId, List.class); + if(list != null && !list.isEmpty()){ + // 先删除 + CacheUtil.del(PREFIX_ID_ROLES + userId); + // 存入缓存 + CacheUtil.put(PREFIX_ID_ROLES + userId, list); + // 清除空拦截 + CacheUtil.putNilFlag(PREFIX_ID_ROLES + userId); + } + } + + /** + * 刷新用户权限 + * @param userId + * @param permissions + * @return + */ + public static void refreshUserAllPerms(String userId, List permissions){ + if(permissions == null || permissions.isEmpty()){ + return; + } + + List list = CacheUtil.get(PREFIX_ID_PERMISSIONS + userId, List.class); + if(list != null && !list.isEmpty()){ + // 先删除 + CacheUtil.del(PREFIX_ID_PERMISSIONS + userId); + // 存入缓存 + CacheUtil.put(PREFIX_ID_PERMISSIONS + userId, list); + // 清除空拦截 + CacheUtil.putNilFlag(PREFIX_ID_PERMISSIONS + userId); + } + } + + /** + * 刷新用户菜单 + * @param userId + * @param menus + * @return + */ + public static void refreshUserMenus(String userId, List menus){ + if(menus == null || menus.isEmpty()){ + return; + } + + List list = CacheUtil.get(PREFIX_ID_MENUS + userId, List.class); + if(list != null && !list.isEmpty()){ + // 先删除 + CacheUtil.del(PREFIX_ID_MENUS + userId); + // 存入缓存 + CacheUtil.put(PREFIX_ID_MENUS + userId, list); + // 清除空拦截 + CacheUtil.putNilFlag(PREFIX_ID_MENUS + userId); + } + } /** * 获得 租户ID * @return */ public static String getTenantId(){ - // TODO 如果 没取到多租户ID 也按照默认值赋值 且不可删除默认多租户数据 - // TODO 判断权限 如果是 admin 超级管理员 则租户ID清空 且findList 不做处理 否则默认都会做处理 - // TODO 如果表中 没有 tenant_id 字段 则不进行多租户处理 + // 判断权限 如果是 admin 超级管理员 则租户ID清空 且findList 不做处理 否则默认都会做处理 + // 如果表中 没有 tenant_id 字段 则不进行多租户处理 - return "a121321255"; + UserModel user = getUser(); + if(user == null){ + throw new RuntimeException("用户为空"); + } + + // 如果是超级管理员 则不进行租户处理 + if(SUPER_ADMIN.equals(user.getUsername())){ + return null; + } + return user.getTenantId(); + } + + + // ===================================== + + @Autowired + public void setRedisLockPlugins(RedisLockPlugins redisLockPlugins) { + UserUtil.redisLockPlugins = redisLockPlugins; } + @Autowired + public void setUserApi(UserApi userApi) { + UserUtil.userApi = userApi; + } } diff --git a/opsli-base-support/opsli-core/src/main/resources/index.html b/opsli-base-support/opsli-core/src/main/resources/index.html new file mode 100644 index 0000000..93a5f6a --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/resources/index.html @@ -0,0 +1 @@ +123123 \ No newline at end of file diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/SystemMsg.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/SystemMsg.java index 6789b2b..e262b8a 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/SystemMsg.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/SystemMsg.java @@ -15,9 +15,18 @@ public enum SystemMsg implements BaseMsg { /** * 数据字典 */ - EXCEL_DICT_UNIQUE(20000,"字典编号重复,该字典已存在!"), - EXCEL_DICT_DETAIL_UNIQUE(20001,"字典名称或值重复,该字典已存在!"), + EXCEPTION_DICT_UNIQUE(20000,"字典编号重复,该字典已存在!"), + EXCEPTION_DICT_DETAIL_UNIQUE(20001,"字典名称或值重复,该字典已存在!"), + /** + * 角色 + */ + EXCEPTION_ROLE_UNIQUE(20000,"角色编号重复,该角色已存在!"), + + /** + * 用户 + */ + EXCEPTION_USER_UNIQUE(20000,"该用户已存在!"), ; diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/depart/api/README.md b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/depart/api/README.md new file mode 100644 index 0000000..f2f9594 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/depart/api/README.md @@ -0,0 +1 @@ +## 改造微服务时 将Feign 接口写在这里 \ No newline at end of file diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/entity/SysDict.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/entity/SysDict.java index c0630ba..5825604 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/entity/SysDict.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/entity/SysDict.java @@ -24,7 +24,7 @@ public class SysDict extends BaseEntity { /** 字典类型名称 */ private String typeName; - /** 是否内置数据 */ + /** 是否内置数据 0是 1否*/ private Character izLock; /** 备注 */ diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/entity/SysDictDetail.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/entity/SysDictDetail.java index f273c99..d45b19b 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/entity/SysDictDetail.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/entity/SysDictDetail.java @@ -29,7 +29,7 @@ public class SysDictDetail extends BaseEntity { /** 字典值 */ private String dictValue; - /** 是否内置数据 */ + /** 是否内置数据 0是 1否*/ private Character izLock; /** 排序 */ diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/IDictDetailService.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/IDictDetailService.java index 77458db..de20896 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/IDictDetailService.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/IDictDetailService.java @@ -1,6 +1,6 @@ package org.opsli.modulars.system.dict.service; -import org.opsli.api.wrapper.system.dict.SysDictDetailModel; +import org.opsli.api.wrapper.system.dict.DictDetailModel; import org.opsli.core.base.service.interfaces.CrudServiceInterface; import org.opsli.modulars.system.dict.entity.SysDictDetail; @@ -13,7 +13,7 @@ import java.util.List; * @CreateTime: 2020-09-17 13:07 * @Description: 数据字典 明细 接口 */ -public interface IDictDetailService extends CrudServiceInterface { +public interface IDictDetailService extends CrudServiceInterface { /** * 根据父类ID 删除 @@ -28,6 +28,6 @@ public interface IDictDetailService extends CrudServiceInterface findListByTypeCode(String typeCode); + List findListByTypeCode(String typeCode); } diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/IDictService.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/IDictService.java index 10d005b..8ea6177 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/IDictService.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/IDictService.java @@ -1,6 +1,6 @@ package org.opsli.modulars.system.dict.service; -import org.opsli.api.wrapper.system.dict.SysDictModel; +import org.opsli.api.wrapper.system.dict.DictModel; import org.opsli.core.base.service.interfaces.CrudServiceInterface; import org.opsli.modulars.system.dict.entity.SysDict; @@ -12,7 +12,7 @@ import org.opsli.modulars.system.dict.entity.SysDict; * @CreateTime: 2020-09-17 13:07 * @Description: 数据字典 接口 */ -public interface IDictService extends CrudServiceInterface { +public interface IDictService extends CrudServiceInterface { } diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/impl/DictDetailServiceImpl.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/impl/DictDetailServiceImpl.java index 353efee..b65e7ed 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/impl/DictDetailServiceImpl.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/impl/DictDetailServiceImpl.java @@ -4,13 +4,12 @@ import cn.hutool.core.convert.Convert; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; +import org.opsli.api.wrapper.system.dict.DictDetailModel; import org.opsli.api.wrapper.system.dict.DictModel; -import org.opsli.api.wrapper.system.dict.SysDictDetailModel; -import org.opsli.api.wrapper.system.dict.SysDictModel; +import org.opsli.api.wrapper.system.dict.DictWrapper; import org.opsli.common.constants.MyBatisConstants; import org.opsli.common.exception.ServiceException; import org.opsli.common.utils.HumpUtil; -import org.opsli.common.utils.WrapperUtil; import org.opsli.core.base.service.impl.CrudServiceImpl; import org.opsli.core.cache.pushsub.enums.CacheType; import org.opsli.core.cache.pushsub.msgs.DictMsgFactory; @@ -39,7 +38,7 @@ import java.util.List; * @Description: 数据字典 明细 接口实现类 */ @Service -public class DictDetailServiceImpl extends CrudServiceImpl implements IDictDetailService { +public class DictDetailServiceImpl extends CrudServiceImpl implements IDictDetailService { @Autowired(required = false) private DictDetailMapper mapper; @@ -55,35 +54,35 @@ public class DictDetailServiceImpl extends CrudServiceImpl 0){ // 重复 - throw new ServiceException(SystemMsg.EXCEL_DICT_DETAIL_UNIQUE); + throw new ServiceException(SystemMsg.EXCEPTION_DICT_DETAIL_UNIQUE); } - SysDictDetailModel ret = super.insert(model); + DictDetailModel ret = super.insert(model); if(ret != null){ - DictModel dictModel = new DictModel(); - dictModel.setTypeCode(model.getTypeCode()); - dictModel.setDictName(model.getDictName()); - dictModel.setDictValue(model.getDictValue()); - dictModel.setModel(ret); + DictWrapper dictWrapperModel = new DictWrapper(); + dictWrapperModel.setTypeCode(model.getTypeCode()); + dictWrapperModel.setDictName(model.getDictName()); + dictWrapperModel.setDictValue(model.getDictValue()); + dictWrapperModel.setModel(ret); // 先删老缓存 - DictUtil.del(dictModel); + DictUtil.del(dictWrapperModel); // 广播缓存数据 - 通知其他服务器同步数据 redisPlugin.sendMessage( - DictMsgFactory.createMsg(dictModel, CacheType.DELETE) + DictMsgFactory.createMsg(dictWrapperModel, CacheType.DELETE) ); // 再存 防止脏数据 - DictUtil.put(dictModel); + DictUtil.put(dictWrapperModel); // 广播缓存数据 - 通知其他服务器同步数据 redisPlugin.sendMessage( - DictMsgFactory.createMsg(dictModel, CacheType.UPDATE) + DictMsgFactory.createMsg(dictWrapperModel, CacheType.UPDATE) ); } @@ -97,43 +96,43 @@ public class DictDetailServiceImpl extends CrudServiceImpl 0){ // 重复 - throw new ServiceException(SystemMsg.EXCEL_DICT_DETAIL_UNIQUE); + throw new ServiceException(SystemMsg.EXCEPTION_DICT_DETAIL_UNIQUE); } // 旧数据 用于删除老缓存 - SysDictDetailModel oldModel = this.get(model); + DictDetailModel oldModel = this.get(model); - SysDictDetailModel ret = super.update(model); + DictDetailModel ret = super.update(model); if(ret != null){ // 先删老缓存 - DictModel oldDictModel = new DictModel(); - oldDictModel.setTypeCode(oldModel.getTypeCode()); - oldDictModel.setDictName(oldModel.getDictName()); - oldDictModel.setDictValue(oldModel.getDictValue()); - DictUtil.del(oldDictModel); + DictWrapper oldDictWrapperModel = new DictWrapper(); + oldDictWrapperModel.setTypeCode(oldModel.getTypeCode()); + oldDictWrapperModel.setDictName(oldModel.getDictName()); + oldDictWrapperModel.setDictValue(oldModel.getDictValue()); + DictUtil.del(oldDictWrapperModel); // 广播缓存数据 - 通知其他服务器同步数据 redisPlugin.sendMessage( - DictMsgFactory.createMsg(oldDictModel, CacheType.DELETE) + DictMsgFactory.createMsg(oldDictWrapperModel, CacheType.DELETE) ); // 再put新缓存 - DictModel dictModel = new DictModel(); - dictModel.setTypeCode(model.getTypeCode()); - dictModel.setDictName(model.getDictName()); - dictModel.setDictValue(model.getDictValue()); - dictModel.setModel(ret); - DictUtil.put(dictModel); + DictWrapper dictWrapperModel = new DictWrapper(); + dictWrapperModel.setTypeCode(model.getTypeCode()); + dictWrapperModel.setDictName(model.getDictName()); + dictWrapperModel.setDictValue(model.getDictValue()); + dictWrapperModel.setModel(ret); + DictUtil.put(dictWrapperModel); // 广播缓存数据 - 通知其他服务器同步数据 redisPlugin.sendMessage( - DictMsgFactory.createMsg(dictModel, CacheType.UPDATE) + DictMsgFactory.createMsg(dictWrapperModel, CacheType.UPDATE) ); } @@ -148,18 +147,18 @@ public class DictDetailServiceImpl extends CrudServiceImpl 0){ - List dictModels = Lists.newArrayListWithCapacity(list.size()); + List dictWrapperModels = Lists.newArrayListWithCapacity(list.size()); // 删除缓存 for (SysDictDetail sysDictDetail : list) { - DictModel dictModel = new DictModel(); - dictModel.setTypeCode(sysDictDetail.getTypeCode()); - dictModel.setDictName(sysDictDetail.getDictName()); - dictModel.setDictValue(sysDictDetail.getDictValue()); + DictWrapper dictWrapperModel = new DictWrapper(); + dictWrapperModel.setTypeCode(sysDictDetail.getTypeCode()); + dictWrapperModel.setDictName(sysDictDetail.getDictName()); + dictWrapperModel.setDictValue(sysDictDetail.getDictValue()); // 删除缓存 - DictUtil.del(dictModel); - dictModels.add(dictModel); + DictUtil.del(dictWrapperModel); + dictWrapperModels.add(dictWrapperModel); } // 广播缓存数据 - 通知其他服务器同步数据 redisPlugin.sendMessage( - DictMsgFactory.createMsg(dictModels, CacheType.DELETE) + DictMsgFactory.createMsg(dictWrapperModels, CacheType.DELETE) ); } @@ -232,13 +231,13 @@ public class DictDetailServiceImpl extends CrudServiceImpl models) { + public boolean deleteAll(Collection models) { QueryBuilder queryBuilder = new GenQueryBuilder<>(); QueryWrapper queryWrapper = queryBuilder.build(); List idList = Lists.newArrayListWithCapacity(models.size()); - for (SysDictDetailModel model : models) { + for (DictDetailModel model : models) { idList.add(model.getId()); } queryWrapper.in(HumpUtil.humpToUnderline(MyBatisConstants.FIELD_ID),idList); @@ -249,20 +248,20 @@ public class DictDetailServiceImpl extends CrudServiceImpl 0){ - List dictModels = Lists.newArrayListWithCapacity(list.size()); + List dictWrapperModels = Lists.newArrayListWithCapacity(list.size()); // 删除缓存 for (SysDictDetail sysDictDetail : list) { - DictModel dictModel = new DictModel(); - dictModel.setTypeCode(sysDictDetail.getTypeCode()); - dictModel.setDictName(sysDictDetail.getDictName()); - dictModel.setDictValue(sysDictDetail.getDictValue()); + DictWrapper dictWrapperModel = new DictWrapper(); + dictWrapperModel.setTypeCode(sysDictDetail.getTypeCode()); + dictWrapperModel.setDictName(sysDictDetail.getDictName()); + dictWrapperModel.setDictValue(sysDictDetail.getDictValue()); // 删除缓存 - DictUtil.del(dictModel); - dictModels.add(dictModel); + DictUtil.del(dictWrapperModel); + dictWrapperModels.add(dictWrapperModel); } // 广播缓存数据 - 通知其他服务器同步数据 redisPlugin.sendMessage( - DictMsgFactory.createMsg(dictModels, CacheType.DELETE) + DictMsgFactory.createMsg(dictWrapperModels, CacheType.DELETE) ); } } @@ -286,22 +285,22 @@ public class DictDetailServiceImpl extends CrudServiceImpl listByTypeCode = this.findListByTypeCode(sysDictModel.getTypeCode()); + DictModel dictModel = iDictService.get(parentId); + List listByTypeCode = this.findListByTypeCode(dictModel.getTypeCode()); if(listByTypeCode != null && listByTypeCode.size() > 0){ - List dictList = Lists.newArrayListWithCapacity(listByTypeCode.size()); - for (SysDictDetailModel sysDictDetailModel : listByTypeCode) { - DictModel dictModel = new DictModel(); - dictModel.setTypeCode(sysDictDetailModel.getTypeCode()); - dictModel.setDictName(sysDictDetailModel.getDictName()); - dictModel.setDictValue(sysDictDetailModel.getDictValue()); - dictList.add(dictModel); + List dictWrapperList = Lists.newArrayListWithCapacity(listByTypeCode.size()); + for (DictDetailModel dictDetailModel : listByTypeCode) { + DictWrapper dictWrapperModel = new DictWrapper(); + dictWrapperModel.setTypeCode(dictDetailModel.getTypeCode()); + dictWrapperModel.setDictName(dictDetailModel.getDictName()); + dictWrapperModel.setDictValue(dictDetailModel.getDictValue()); + dictWrapperList.add(dictWrapperModel); } // 删除缓存 - DictUtil.delAll(sysDictModel.getTypeCode()); + DictUtil.delAll(dictModel.getTypeCode()); // 广播缓存数据 - 通知其他服务器同步数据 redisPlugin.sendMessage( - DictMsgFactory.createMsg(dictList, CacheType.DELETE) + DictMsgFactory.createMsg(dictWrapperList, CacheType.DELETE) ); } } @@ -315,7 +314,7 @@ public class DictDetailServiceImpl extends CrudServiceImpl findListByTypeCode(String typeCode) { + public List findListByTypeCode(String typeCode) { if(StringUtils.isEmpty(typeCode)) return null; String key = HumpUtil.humpToUnderline("typeCode"); @@ -327,7 +326,7 @@ public class DictDetailServiceImpl extends CrudServiceImpl list = this.findList(queryWrapper); // 转化对象 - return WrapperUtil.transformInstance(list, SysDictDetailModel.class); + return super.transformTs2Ms(list); } } diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/impl/DictServiceImpl.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/impl/DictServiceImpl.java index 1ff4c2f..b0e80f3 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/impl/DictServiceImpl.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/service/impl/DictServiceImpl.java @@ -1,10 +1,9 @@ package org.opsli.modulars.system.dict.service.impl; import org.apache.commons.lang3.StringUtils; -import org.opsli.api.wrapper.system.dict.SysDictDetailModel; -import org.opsli.api.wrapper.system.dict.SysDictModel; +import org.opsli.api.wrapper.system.dict.DictDetailModel; +import org.opsli.api.wrapper.system.dict.DictModel; import org.opsli.common.exception.ServiceException; -import org.opsli.common.utils.WrapperUtil; import org.opsli.core.base.service.impl.CrudServiceImpl; import org.opsli.modulars.system.SystemMsg; import org.opsli.modulars.system.dict.entity.SysDict; @@ -27,7 +26,7 @@ import java.util.List; * @Description: 数据字典 接口实现类 */ @Service -public class DictServiceImpl extends CrudServiceImpl implements IDictService { +public class DictServiceImpl extends CrudServiceImpl implements IDictService { @Autowired(required = false) private DictMapper mapper; @@ -35,15 +34,15 @@ public class DictServiceImpl extends CrudServiceImpl 0){ // 重复 - throw new ServiceException(SystemMsg.EXCEL_DICT_UNIQUE); + throw new ServiceException(SystemMsg.EXCEPTION_DICT_UNIQUE); } return super.insert(model); @@ -51,28 +50,28 @@ public class DictServiceImpl extends CrudServiceImpl 0){ // 重复 - throw new ServiceException(SystemMsg.EXCEL_DICT_UNIQUE); + throw new ServiceException(SystemMsg.EXCEPTION_DICT_UNIQUE); } - SysDictModel updateRet = super.update(model); + DictModel updateRet = super.update(model); // 字典主表修改 子表跟着联动 (验证是否改了编号)/ 或者修改不允许改编号 - List listByTypeCode = null; + List listByTypeCode = null; if(StringUtils.isNotEmpty(model.getTypeCode())){ listByTypeCode = iDictDetailService.findListByTypeCode(model.getTypeCode()); } if(listByTypeCode != null && listByTypeCode.size() > 0){ - for (SysDictDetailModel sysDictDetailModel : listByTypeCode) { - sysDictDetailModel.setTypeCode(updateRet.getTypeCode()); - iDictDetailService.update(sysDictDetailModel); + for (DictDetailModel dictDetailModel : listByTypeCode) { + dictDetailModel.setTypeCode(updateRet.getTypeCode()); + iDictDetailService.update(dictDetailModel); } } @@ -91,7 +90,7 @@ public class DictServiceImpl extends CrudServiceImpl models) { + public boolean deleteAll(Collection models) { if(models == null || models.isEmpty()) return false; // 删除字典明细表 - for (SysDictModel model : models) { + for (DictModel model : models) { if(model == null || StringUtils.isEmpty(model.getId())){ continue; } diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/web/DictDetailRestController.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/web/DictDetailRestController.java index f7bee27..c4f7369 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/web/DictDetailRestController.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/web/DictDetailRestController.java @@ -4,7 +4,7 @@ import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.opsli.api.base.result.ResultVo; import org.opsli.api.web.system.dict.DictDetailApi; -import org.opsli.api.wrapper.system.dict.SysDictDetailModel; +import org.opsli.api.wrapper.system.dict.DictDetailModel; import org.opsli.common.annotation.ApiRestController; import org.opsli.core.base.concroller.BaseRestController; import org.opsli.core.persistence.Page; @@ -27,8 +27,8 @@ import java.util.List; * @Description: 数据字典明细 */ @Slf4j -@ApiRestController("/dict/detail") -public class DictDetailRestController extends BaseRestController +@ApiRestController("/sys/dict/detail") +public class DictDetailRestController extends BaseRestController implements DictDetailApi { @@ -39,7 +39,11 @@ public class DictDetailRestController extends BaseRestController get(SysDictDetailModel model) { + public ResultVo get(DictDetailModel model) { + // 如果系统内部调用 则直接查数据库 + if(model != null && model.getIzApi() != null && model.getIzApi()){ + model = IService.get(model); + } return ResultVo.success(model); } @@ -55,7 +59,7 @@ public class DictDetailRestController extends BaseRestController findPage(Integer pageNo, Integer pageSize, HttpServletRequest request) { QueryBuilder queryBuilder = new WebQueryBuilder<>(SysDictDetail.class, request.getParameterMap()); - Page page = new Page<>(pageNo, pageSize); + Page page = new Page<>(pageNo, pageSize); page.setQueryWrapper(queryBuilder.build()); page = IService.findPage(page); @@ -69,7 +73,7 @@ public class DictDetailRestController extends BaseRestController insert(SysDictDetailModel model) { + public ResultVo insert(DictDetailModel model) { // 调用新增方法 IService.insert(model); return ResultVo.success("新增字典数据成功"); @@ -82,7 +86,7 @@ public class DictDetailRestController extends BaseRestController update(SysDictDetailModel model) { + public ResultVo update(DictDetailModel model) { // 调用修改方法 IService.update(model); return ResultVo.success("修改字典数据"); @@ -159,7 +163,7 @@ public class DictDetailRestController extends BaseRestController> findListByTypeCode(String typeCode) { + public ResultVo> findListByTypeCode(String typeCode) { return ResultVo.success(IService.findListByTypeCode(typeCode)); } } diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/web/DictRestController.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/web/DictRestController.java index f285d22..c6a3f54 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/web/DictRestController.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/dict/web/DictRestController.java @@ -4,9 +4,9 @@ import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.opsli.api.base.result.ResultVo; import org.opsli.api.web.system.dict.DictApi; -import org.opsli.api.web.system.dict.DictDetailApi; -import org.opsli.api.wrapper.system.dict.SysDictModel; +import org.opsli.api.wrapper.system.dict.DictModel; import org.opsli.common.annotation.ApiRestController; +import org.opsli.common.api.TokenThreadLocal; import org.opsli.core.base.concroller.BaseRestController; import org.opsli.core.persistence.Page; import org.opsli.core.persistence.querybuilder.QueryBuilder; @@ -27,8 +27,8 @@ import javax.servlet.http.HttpServletResponse; * @Description: 数据字典 */ @Slf4j -@ApiRestController("/dict") -public class DictRestController extends BaseRestController +@ApiRestController("/sys/dict") +public class DictRestController extends BaseRestController implements DictApi { @@ -39,7 +39,12 @@ public class DictRestController extends BaseRestController get(SysDictModel model) { + public ResultVo get(DictModel model) { + // 如果系统内部调用 则直接查数据库 + if(model != null && model.getIzApi() != null && model.getIzApi()){ + model = IService.get(model); + } + System.out.println(TokenThreadLocal.get()); return ResultVo.success(model); } @@ -55,7 +60,7 @@ public class DictRestController extends BaseRestController findPage(Integer pageNo, Integer pageSize, HttpServletRequest request) { QueryBuilder queryBuilder = new WebQueryBuilder<>(SysDict.class, request.getParameterMap()); - Page page = new Page<>(pageNo, pageSize); + Page page = new Page<>(pageNo, pageSize); page.setQueryWrapper(queryBuilder.build()); page = IService.findPage(page); @@ -69,7 +74,7 @@ public class DictRestController extends BaseRestController insert(SysDictModel model) { + public ResultVo insert(DictModel model) { // 调用新增方法 IService.insert(model); return ResultVo.success("新增字典明细数据成功"); @@ -82,7 +87,7 @@ public class DictRestController extends BaseRestController update(SysDictModel model) { + public ResultVo update(DictModel model) { // 调用修改方法 IService.update(model); return ResultVo.success("修改字典明细数据成功"); @@ -125,7 +130,7 @@ public class DictRestController extends BaseRestController exportExcel(HttpServletRequest request, HttpServletResponse response) { QueryBuilder queryBuilder = new WebQueryBuilder<>(SysDict.class, request.getParameterMap()); - return super.excelExport(DictDetailApi.TITLE, queryBuilder.build(), response); + return super.excelExport(DictApi.TITLE, queryBuilder.build(), response); } /** @@ -147,7 +152,7 @@ public class DictRestController extends BaseRestController importTemplate(HttpServletResponse response) { - return super.importTemplate(DictDetailApi.TITLE, response); + return super.importTemplate(DictApi.TITLE, response); } } diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginForm.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginForm.java new file mode 100644 index 0000000..deebff9 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginForm.java @@ -0,0 +1,49 @@ +/** + * + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.opsli.modulars.system.login.entity; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 登录表单 + * + * @author liuzp + * @since 2.0.0 2018-01-25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class LoginForm { + + /** 用户名 */ + @ApiModelProperty(value = "用户名") + private String username; + + /** 密码 */ + @ApiModelProperty(value = "密码") + private String password; + + /** 验证码 */ + @ApiModelProperty(value = "验证码") + private String captcha; + + /** UUID */ + @ApiModelProperty(value = "UUID") + private String uuid; + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/web/LoginRestController.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/web/LoginRestController.java new file mode 100644 index 0000000..7e5f2d3 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/web/LoginRestController.java @@ -0,0 +1,99 @@ +package org.opsli.modulars.system.login.web; + +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.crypto.hash.Md5Hash; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.wrapper.system.user.UserModel; +import org.opsli.common.api.TokenThreadLocal; +import org.opsli.core.utils.CaptchaUtil; +import org.opsli.core.utils.UserTokenUtil; +import org.opsli.core.utils.UserUtil; +import org.opsli.modulars.system.login.entity.LoginForm; +import org.springframework.web.bind.annotation.*; + +import javax.imageio.ImageIO; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.awt.image.BufferedImage; +import java.io.IOException; + +/** + * 登陆 / 登出 / 验证码 + * + * @author parker + * @date 2020-05-23 13:30 + * + * 不需要继承 api 接口 + * + */ +@Slf4j +@RestController +public class LoginRestController { + + + /** + * 登录 + */ + @ApiOperation(value = "登录", notes = "登录") + @PostMapping("/sys/login") + public ResultVo login(@RequestBody LoginForm form){ + boolean captcha = CaptchaUtil.validate(form.getUuid(), form.getCaptcha()); + if(!captcha){ + return ResultVo.error("验证码不正确"); + } + + //用户信息 + UserModel user = UserUtil.getUserByUserName(form.getUsername());; + + //账号不存在、密码错误 + if(user == null || + !user.getPassword().equals(new Md5Hash(form.getPassword(), user.getSecretkey()).toHex())) { + return ResultVo.error("账号或密码不正确"); + } + + //账号锁定 + if(user.getLocked() == 1){ + return ResultVo.error("账号已被锁定,请联系管理员"); + } + + //生成token,并保存到Redis + return UserTokenUtil.createToken(user); + } + + + /** + * 登出 + */ + @ApiOperation(value = "登出", notes = "登出") + @PostMapping("/sys/logout") + public ResultVo logout() { + String token = TokenThreadLocal.get(); + if(StringUtils.isEmpty(token)){ + return ResultVo.success("登出失败,没有授权Token!"); + } + UserTokenUtil.logout(token); + return ResultVo.success("登出成功!"); + } + + /** + * 验证码 + */ + @ApiOperation(value = "验证码", notes = "验证码") + @GetMapping("captcha.jpg") + public void captcha(String uuid, HttpServletResponse response) throws IOException { + response.setHeader("Cache-Control", "no-store, no-cache"); + response.setContentType("image/jpeg"); + + //获取图片验证码 + BufferedImage image = CaptchaUtil.getCaptcha(uuid); + if(image != null){ + ServletOutputStream out = response.getOutputStream(); + ImageIO.write(image, "jpg", out); + IOUtils.closeQuietly(out); + } + } + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/api/README.md b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/api/README.md new file mode 100644 index 0000000..f2f9594 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/api/README.md @@ -0,0 +1 @@ +## 改造微服务时 将Feign 接口写在这里 \ No newline at end of file diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/entity/SysMenu.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/entity/SysMenu.java new file mode 100644 index 0000000..70f17a0 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/entity/SysMenu.java @@ -0,0 +1,45 @@ +package org.opsli.modulars.system.menu.entity; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.opsli.core.base.entity.BaseEntity; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.entity + * @Author: Parker + * @CreateTime: 2020-09-16 17:33 + * @Description: 菜单表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SysMenu extends BaseEntity { + + + /** 父级主键 */ + private String parentId; + + /** 菜单编号 */ + private String menuCode; + + /** 菜单名称 */ + private String menuName; + + /** 图标 */ + private String icon; + + /** 项目类型:1-菜单2-按钮3-链接4-表单 */ + private String type; + + /** url地址 */ + private String url; + + + // ======================================== + + /** 逻辑删除字段 */ + @TableLogic + private Integer deleted; + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/mapper/MenuMapper.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/mapper/MenuMapper.java new file mode 100644 index 0000000..bf0f24b --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/mapper/MenuMapper.java @@ -0,0 +1,24 @@ +package org.opsli.modulars.system.menu.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.opsli.modulars.system.menu.entity.SysMenu; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.mapper + * @Author: Parker + * @CreateTime: 2020-09-17 13:01 + * @Description: 角色 Mapper + */ +@Mapper +public interface MenuMapper extends BaseMapper { + + /** + * 唯一验证 + * @param entity + * @return + */ + Integer uniqueVerificationByCode(SysMenu entity); + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/mapper/xml/RoleMapper.xml b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/mapper/xml/RoleMapper.xml new file mode 100644 index 0000000..5798629 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/mapper/xml/RoleMapper.xml @@ -0,0 +1,41 @@ + + + + + + + a.id as id, + + a.parent_id as parentId, + a.menu_code as menuCode, + a.menu_name as menuName, + a.icon as icon, + a.type as type, + a.url as url, + a.iz_lock as izLock, + + a.create_by as createBy, + a.create_time as createTime, + a.update_by as updateBy, + a.update_time as updateTime, + a.version as version, + a.deleted as deleted + + + + + + + + \ No newline at end of file diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/service/IMenuService.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/service/IMenuService.java new file mode 100644 index 0000000..ce34a9e --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/service/IMenuService.java @@ -0,0 +1,18 @@ +package org.opsli.modulars.system.menu.service; + +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.core.base.service.interfaces.CrudServiceInterface; +import org.opsli.modulars.system.menu.entity.SysMenu; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.service + * @Author: Parker + * @CreateTime: 2020-09-17 13:07 + * @Description: 角色 接口 + */ +public interface IMenuService extends CrudServiceInterface { + + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/service/impl/MenuServiceImpl.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/service/impl/MenuServiceImpl.java new file mode 100644 index 0000000..c4d4dce --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/service/impl/MenuServiceImpl.java @@ -0,0 +1,62 @@ +package org.opsli.modulars.system.menu.service.impl; + +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.common.exception.ServiceException; +import org.opsli.common.utils.WrapperUtil; +import org.opsli.core.base.service.impl.CrudServiceImpl; +import org.opsli.modulars.system.SystemMsg; +import org.opsli.modulars.system.menu.entity.SysMenu; +import org.opsli.modulars.system.menu.mapper.MenuMapper; +import org.opsli.modulars.system.menu.service.IMenuService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.service + * @Author: Parker + * @CreateTime: 2020-09-16 17:34 + * @Description: 角色 接口实现类 + */ +@Service +public class MenuServiceImpl extends CrudServiceImpl implements IMenuService { + + @Autowired(required = false) + private MenuMapper mapper; + + @Override + public MenuModel insert(MenuModel model) { + if(model == null) return null; + + SysMenu entity = super.transformM2T(model); + // 唯一验证 + Integer count = mapper.uniqueVerificationByCode(entity); + if(count != null && count > 0){ + // 重复 + throw new ServiceException(SystemMsg.EXCEPTION_ROLE_UNIQUE); + } + + return super.insert(model); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public MenuModel update(MenuModel model) { + if(model == null) return null; + + SysMenu entity = super.transformM2T(model); + // 唯一验证 + Integer count = mapper.uniqueVerificationByCode(entity); + if(count != null && count > 0){ + // 重复 + throw new ServiceException(SystemMsg.EXCEPTION_ROLE_UNIQUE); + } + + return super.update(model); + } + +} + + diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/web/MenuRestController.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/web/MenuRestController.java new file mode 100644 index 0000000..a0c2b16 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/menu/web/MenuRestController.java @@ -0,0 +1,156 @@ +package org.opsli.modulars.system.menu.web; + +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.web.system.menu.MenuApi; +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.common.annotation.ApiRestController; +import org.opsli.core.base.concroller.BaseRestController; +import org.opsli.core.persistence.Page; +import org.opsli.core.persistence.querybuilder.QueryBuilder; +import org.opsli.core.persistence.querybuilder.WebQueryBuilder; +import org.opsli.modulars.system.menu.entity.SysMenu; +import org.opsli.modulars.system.menu.service.IMenuService; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.web + * @Author: Parker + * @CreateTime: 2020-09-13 17:40 + * @Description: 菜单 + */ +@Slf4j +@ApiRestController("/sys/menu") +public class MenuRestController extends BaseRestController + implements MenuApi { + + + /** + * 菜单 查一条 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "获得单条字典明细数据", notes = "获得单条字典明细数据 - ID") + @Override + public ResultVo get(MenuModel model) { + // 如果系统内部调用 则直接查数据库 + if(model != null && model.getIzApi() != null && model.getIzApi()){ + model = IService.get(model); + } + return ResultVo.success(model); + } + + /** + * 菜单 查询分页 + * @param pageNo 当前页 + * @param pageSize 每页条数 + * @param request request + * @return ResultVo + */ + @ApiOperation(value = "获得分页数据", notes = "获得分页数据 - 查询构造器") + @Override + public ResultVo findPage(Integer pageNo, Integer pageSize, HttpServletRequest request) { + + QueryBuilder queryBuilder = new WebQueryBuilder<>(SysMenu.class, request.getParameterMap()); + Page page = new Page<>(pageNo, pageSize); + page.setQueryWrapper(queryBuilder.build()); + page = IService.findPage(page); + + return ResultVo.success(page.getBootstrapData()); + } + + /** + * 菜单 新增 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "新增字典明细数据", notes = "新增字典明细数据") + @Override + public ResultVo insert(MenuModel model) { + // 调用新增方法 + IService.insert(model); + return ResultVo.success("新增字典明细数据成功"); + } + + /** + * 菜单 修改 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "修改字典明细数据", notes = "修改字典明细数据") + @Override + public ResultVo update(MenuModel model) { + // 调用修改方法 + IService.update(model); + return ResultVo.success("修改字典明细数据成功"); + } + + + /** + * 菜单 删除 + * @param id ID + * @return ResultVo + */ + @ApiOperation(value = "删除字典明细数据数据", notes = "删除字典明细数据数据") + @Override + public ResultVo del(String id){ + IService.delete(id); + return ResultVo.success("删除字典明细数据成功"); + } + + + /** + * 菜单 批量删除 + * @param ids ID 数组 + * @return ResultVo + */ + @ApiOperation(value = "批量删除字典明细数据数据", notes = "批量删除字典明细数据数据") + @Override + public ResultVo delAll(String[] ids){ + IService.deleteAll(ids); + return ResultVo.success("批量删除字典明细数据成功"); + } + + + /** + * 菜单 Excel 导出 + * @param request request + * @param response response + * @return ResultVo + */ + @ApiOperation(value = "导出Excel", notes = "导出Excel") + @Override + public ResultVo exportExcel(HttpServletRequest request, HttpServletResponse response) { + QueryBuilder queryBuilder = new WebQueryBuilder<>(SysMenu.class, request.getParameterMap()); + return super.excelExport(MenuApi.TITLE, queryBuilder.build(), response); + } + + /** + * 菜单 Excel 导入 + * @param request 文件流 request + * @return ResultVo + */ + @ApiOperation(value = "导入Excel", notes = "导入Excel") + @Override + public ResultVo excelImport(MultipartHttpServletRequest request) { + return super.excelImport(request); + } + + /** + * 菜单 Excel 下载导入模版 + * @param response response + * @return ResultVo + */ + @ApiOperation(value = "导出Excel模版", notes = "导出Excel模版") + @Override + public ResultVo importTemplate(HttpServletResponse response) { + return super.importTemplate(MenuApi.TITLE, response); + } + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/api/README.md b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/api/README.md new file mode 100644 index 0000000..f2f9594 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/api/README.md @@ -0,0 +1 @@ +## 改造微服务时 将Feign 接口写在这里 \ No newline at end of file diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/entity/SysRole.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/entity/SysRole.java new file mode 100644 index 0000000..fcb9b0b --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/entity/SysRole.java @@ -0,0 +1,40 @@ +package org.opsli.modulars.system.role.entity; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.opsli.core.base.entity.BaseEntity; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.entity + * @Author: Parker + * @CreateTime: 2020-09-16 17:33 + * @Description: 角色表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SysRole extends BaseEntity { + + + + /** 角色编码 */ + private String roleCode; + + /** 角色名称 */ + private String roleName; + + /** 是否内置数据 0是 1否*/ + private Character izLock; + + /** 备注 */ + private String remark; + + + // ======================================== + + /** 逻辑删除字段 */ + @TableLogic + private Integer deleted; + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/mapper/RoleMapper.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/mapper/RoleMapper.java new file mode 100644 index 0000000..69a5672 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/mapper/RoleMapper.java @@ -0,0 +1,24 @@ +package org.opsli.modulars.system.role.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.opsli.modulars.system.role.entity.SysRole; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.mapper + * @Author: Parker + * @CreateTime: 2020-09-17 13:01 + * @Description: 角色 Mapper + */ +@Mapper +public interface RoleMapper extends BaseMapper { + + /** + * 唯一验证 + * @param entity + * @return + */ + Integer uniqueVerificationByCode(SysRole entity); + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/mapper/xml/RoleMapper.xml b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/mapper/xml/RoleMapper.xml new file mode 100644 index 0000000..d519a69 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/mapper/xml/RoleMapper.xml @@ -0,0 +1,38 @@ + + + + + + + a.id as id, + + a.role_code as roleCode, + a.role_name as roleName, + a.iz_lock as izLock, + a.remark as remark, + + a.create_by as createBy, + a.create_time as createTime, + a.update_by as updateBy, + a.update_time as updateTime, + a.version as version, + a.deleted as deleted + + + + + + + + \ No newline at end of file diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/service/IRoleService.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/service/IRoleService.java new file mode 100644 index 0000000..3294bab --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/service/IRoleService.java @@ -0,0 +1,18 @@ +package org.opsli.modulars.system.role.service; + +import org.opsli.api.wrapper.system.role.RoleModel; +import org.opsli.core.base.service.interfaces.CrudServiceInterface; +import org.opsli.modulars.system.role.entity.SysRole; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.service + * @Author: Parker + * @CreateTime: 2020-09-17 13:07 + * @Description: 角色 接口 + */ +public interface IRoleService extends CrudServiceInterface { + + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/service/impl/RoleServiceImpl.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..19f4fcf --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/service/impl/RoleServiceImpl.java @@ -0,0 +1,61 @@ +package org.opsli.modulars.system.role.service.impl; + +import org.opsli.api.wrapper.system.role.RoleModel; +import org.opsli.common.exception.ServiceException; +import org.opsli.core.base.service.impl.CrudServiceImpl; +import org.opsli.modulars.system.SystemMsg; +import org.opsli.modulars.system.role.entity.SysRole; +import org.opsli.modulars.system.role.mapper.RoleMapper; +import org.opsli.modulars.system.role.service.IRoleService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.service + * @Author: Parker + * @CreateTime: 2020-09-16 17:34 + * @Description: 角色 接口实现类 + */ +@Service +public class RoleServiceImpl extends CrudServiceImpl implements IRoleService { + + @Autowired(required = false) + private RoleMapper mapper; + + @Override + public RoleModel insert(RoleModel model) { + if(model == null) return null; + + SysRole entity = super.transformM2T(model); + // 唯一验证 + Integer count = mapper.uniqueVerificationByCode(entity); + if(count != null && count > 0){ + // 重复 + throw new ServiceException(SystemMsg.EXCEPTION_ROLE_UNIQUE); + } + + return super.insert(model); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public RoleModel update(RoleModel model) { + if(model == null) return null; + + SysRole entity = super.transformM2T(model); + // 唯一验证 + Integer count = mapper.uniqueVerificationByCode(entity); + if(count != null && count > 0){ + // 重复 + throw new ServiceException(SystemMsg.EXCEPTION_ROLE_UNIQUE); + } + + return super.update(model); + } + +} + + diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/web/RoleRestController.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/web/RoleRestController.java new file mode 100644 index 0000000..a9415a5 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/role/web/RoleRestController.java @@ -0,0 +1,156 @@ +package org.opsli.modulars.system.role.web; + +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.web.system.role.RoleApi; +import org.opsli.api.wrapper.system.role.RoleModel; +import org.opsli.common.annotation.ApiRestController; +import org.opsli.core.base.concroller.BaseRestController; +import org.opsli.core.persistence.Page; +import org.opsli.core.persistence.querybuilder.QueryBuilder; +import org.opsli.core.persistence.querybuilder.WebQueryBuilder; +import org.opsli.modulars.system.role.entity.SysRole; +import org.opsli.modulars.system.role.service.IRoleService; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.web + * @Author: Parker + * @CreateTime: 2020-09-13 17:40 + * @Description: 角色 + */ +@Slf4j +@ApiRestController("/sys/role") +public class RoleRestController extends BaseRestController + implements RoleApi { + + + /** + * 角色 查一条 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "获得单条字典明细数据", notes = "获得单条字典明细数据 - ID") + @Override + public ResultVo get(RoleModel model) { + // 如果系统内部调用 则直接查数据库 + if(model != null && model.getIzApi() != null && model.getIzApi()){ + model = IService.get(model); + } + return ResultVo.success(model); + } + + /** + * 角色 查询分页 + * @param pageNo 当前页 + * @param pageSize 每页条数 + * @param request request + * @return ResultVo + */ + @ApiOperation(value = "获得分页数据", notes = "获得分页数据 - 查询构造器") + @Override + public ResultVo findPage(Integer pageNo, Integer pageSize, HttpServletRequest request) { + + QueryBuilder queryBuilder = new WebQueryBuilder<>(SysRole.class, request.getParameterMap()); + Page page = new Page<>(pageNo, pageSize); + page.setQueryWrapper(queryBuilder.build()); + page = IService.findPage(page); + + return ResultVo.success(page.getBootstrapData()); + } + + /** + * 角色 新增 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "新增字典明细数据", notes = "新增字典明细数据") + @Override + public ResultVo insert(RoleModel model) { + // 调用新增方法 + IService.insert(model); + return ResultVo.success("新增字典明细数据成功"); + } + + /** + * 角色 修改 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "修改字典明细数据", notes = "修改字典明细数据") + @Override + public ResultVo update(RoleModel model) { + // 调用修改方法 + IService.update(model); + return ResultVo.success("修改字典明细数据成功"); + } + + + /** + * 角色 删除 + * @param id ID + * @return ResultVo + */ + @ApiOperation(value = "删除字典明细数据数据", notes = "删除字典明细数据数据") + @Override + public ResultVo del(String id){ + IService.delete(id); + return ResultVo.success("删除字典明细数据成功"); + } + + + /** + * 角色 批量删除 + * @param ids ID 数组 + * @return ResultVo + */ + @ApiOperation(value = "批量删除字典明细数据数据", notes = "批量删除字典明细数据数据") + @Override + public ResultVo delAll(String[] ids){ + IService.deleteAll(ids); + return ResultVo.success("批量删除字典明细数据成功"); + } + + + /** + * 角色 Excel 导出 + * @param request request + * @param response response + * @return ResultVo + */ + @ApiOperation(value = "导出Excel", notes = "导出Excel") + @Override + public ResultVo exportExcel(HttpServletRequest request, HttpServletResponse response) { + QueryBuilder queryBuilder = new WebQueryBuilder<>(SysRole.class, request.getParameterMap()); + return super.excelExport(RoleApi.TITLE, queryBuilder.build(), response); + } + + /** + * 角色 Excel 导入 + * @param request 文件流 request + * @return ResultVo + */ + @ApiOperation(value = "导入Excel", notes = "导入Excel") + @Override + public ResultVo excelImport(MultipartHttpServletRequest request) { + return super.excelImport(request); + } + + /** + * 角色 Excel 下载导入模版 + * @param response response + * @return ResultVo + */ + @ApiOperation(value = "导出Excel模版", notes = "导出Excel模版") + @Override + public ResultVo importTemplate(HttpServletResponse response) { + return super.importTemplate(RoleApi.TITLE, response); + } + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/api/README.md b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/api/README.md new file mode 100644 index 0000000..f2f9594 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/api/README.md @@ -0,0 +1 @@ +## 改造微服务时 将Feign 接口写在这里 \ No newline at end of file diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/entity/SysUser.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/entity/SysUser.java new file mode 100644 index 0000000..22edc55 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/entity/SysUser.java @@ -0,0 +1,62 @@ +package org.opsli.modulars.system.user.entity; + +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.opsli.core.base.entity.BaseEntity; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.entity + * @Author: Parker + * @CreateTime: 2020-09-16 17:33 + * @Description: 用户信息表 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class SysUser extends BaseEntity { + + + /** 登录账户 */ + private String username; + + /** 登录密码 */ + private String password; + + /** 盐值,密码秘钥 */ + private String secretKey; + + /** 是否锁定 */ + private String locked; + + /** 真实姓名 */ + private String realName; + + /** 手机 */ + private String mobile; + + /** 邮箱 */ + private String email; + + /** 工号 */ + private String no; + + /** 头像 */ + private String avatar; + + /** 最后登陆IP */ + private String loginIp; + + /** 备注 */ + private String remark; + + // ======================================== + + /** 逻辑删除字段 */ + @TableLogic + private Integer deleted; + + /** 多租户字段 */ + private String tenantId; + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/mapper/UserMapper.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/mapper/UserMapper.java new file mode 100644 index 0000000..7299a45 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/mapper/UserMapper.java @@ -0,0 +1,48 @@ +package org.opsli.modulars.system.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.opsli.modulars.system.menu.entity.SysMenu; +import org.opsli.modulars.system.user.entity.SysUser; + +import java.util.List; + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.mapper + * @Author: Parker + * @CreateTime: 2020-09-17 13:01 + * @Description: 用户 Mapper + */ +@Mapper +public interface UserMapper extends BaseMapper { + + /** + * 唯一验证 + * @param entity + * @return + */ + Integer uniqueVerificationByUsername(SysUser entity); + + + /** + * 根据用户ID 获得当前角色编码集合 + * @param userId + * @return + */ + List getRoleCodeList(String userId); + + /** + * 根据用户ID 获得权限 + * @param userId + * @return + */ + List queryAllPerms(String userId); + + /** + * 根据用户ID 获得菜单集合 + * @param userId + * @return + */ + List findMenuListByUserId(String userId); +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/mapper/xml/UserMapper.xml b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/mapper/xml/UserMapper.xml new file mode 100644 index 0000000..35526e4 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/mapper/xml/UserMapper.xml @@ -0,0 +1,94 @@ + + + + + + + a.id as id, + + a.username as username, + a.password as password, + a.secret_key as secretKey, + a.no as no, + a.real_name as realName, + a.locked as locked, + a.mobile as mobile, + a.email as email, + a.avatar as avatar, + a.login_ip as loginIp, + a.remark as remark, + a.tenant_id as tenantId, + + a.create_by as createBy, + a.create_time as createTime, + a.update_by as updateBy, + a.update_time as updateTime, + a.version as version, + a.deleted as deleted + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/service/IUserService.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/service/IUserService.java new file mode 100644 index 0000000..75e31e5 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/service/IUserService.java @@ -0,0 +1,48 @@ +package org.opsli.modulars.system.user.service; + +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.api.wrapper.system.user.UserModel; +import org.opsli.core.base.service.interfaces.CrudServiceInterface; +import org.opsli.modulars.system.user.entity.SysUser; + +import java.util.List; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.service + * @Author: Parker + * @CreateTime: 2020-09-17 13:07 + * @Description: 用户 接口 + */ +public interface IUserService extends CrudServiceInterface { + + /** + * 根据 用户名 获得当前用户 + * @param username + * @return + */ + UserModel queryByUserName(String username); + + /** + * 根据用户ID 获得当前角色编码集合 + * @param userId + * @return + */ + List getRoleCodeList(String userId); + + /** + * 根据用户ID 获得权限 + * @param userId + * @return + */ + List getAllPerms(String userId); + + /** + * 根据用户ID 获得菜单集合 + * @param userId + * @return + */ + List getMenuListByUserId(String userId); + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/service/impl/UserServiceImpl.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..e9fe08e --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/service/impl/UserServiceImpl.java @@ -0,0 +1,102 @@ +package org.opsli.modulars.system.user.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.api.wrapper.system.user.UserModel; +import org.opsli.common.exception.ServiceException; +import org.opsli.common.utils.HumpUtil; +import org.opsli.common.utils.WrapperUtil; +import org.opsli.core.base.service.impl.CrudServiceImpl; +import org.opsli.core.persistence.querybuilder.GenQueryBuilder; +import org.opsli.core.persistence.querybuilder.QueryBuilder; +import org.opsli.core.utils.UserUtil; +import org.opsli.modulars.system.SystemMsg; +import org.opsli.modulars.system.menu.entity.SysMenu; +import org.opsli.modulars.system.user.entity.SysUser; +import org.opsli.modulars.system.user.mapper.UserMapper; +import org.opsli.modulars.system.user.service.IUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.service + * @Author: Parker + * @CreateTime: 2020-09-16 17:34 + * @Description: 角色 接口实现类 + */ +@Service +public class UserServiceImpl extends CrudServiceImpl implements IUserService { + + @Autowired(required = false) + private UserMapper mapper; + + @Override + public UserModel insert(UserModel model) { + if(model == null) return null; + + SysUser entity = super.transformM2T(model); + // 唯一验证 + Integer count = mapper.uniqueVerificationByUsername(entity); + if(count != null && count > 0){ + // 重复 + throw new ServiceException(SystemMsg.EXCEPTION_USER_UNIQUE); + } + + return super.insert(model); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public UserModel update(UserModel model) { + if(model == null) return null; + + SysUser entity = super.transformM2T(model); + // 唯一验证 + Integer count = mapper.uniqueVerificationByUsername(entity); + if(count != null && count > 0){ + // 重复 + throw new ServiceException(SystemMsg.EXCEPTION_USER_UNIQUE); + } + + UserModel update = super.update(model); + if(update != null){ + // 刷新用户缓存 + UserUtil.refreshUser(update); + } + + return update; + } + + @Override + public UserModel queryByUserName(String username) { + String key = HumpUtil.humpToUnderline("username"); + QueryBuilder queryBuilder = new GenQueryBuilder<>(); + QueryWrapper queryWrapper = queryBuilder.build(); + queryWrapper.eq(key, username); + SysUser user = this.getOne(queryWrapper); + return super.transformT2M(user); + } + + @Override + public List getRoleCodeList(String userId) { + return mapper.getRoleCodeList(userId); + } + + @Override + public List getAllPerms(String userId) { + return mapper.queryAllPerms(userId); + } + + @Override + public List getMenuListByUserId(String userId) { + List menuList = mapper.findMenuListByUserId(userId); + return WrapperUtil.transformInstance(menuList, MenuModel.class); + } +} + + diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/web/UserRestController.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/web/UserRestController.java new file mode 100644 index 0000000..809fb54 --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/user/web/UserRestController.java @@ -0,0 +1,206 @@ +package org.opsli.modulars.system.user.web; + +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.opsli.api.base.result.ResultVo; +import org.opsli.api.web.system.user.UserApi; +import org.opsli.api.wrapper.system.menu.MenuModel; +import org.opsli.api.wrapper.system.user.UserModel; +import org.opsli.common.annotation.ApiRestController; +import org.opsli.core.base.concroller.BaseRestController; +import org.opsli.core.persistence.Page; +import org.opsli.core.persistence.querybuilder.QueryBuilder; +import org.opsli.core.persistence.querybuilder.WebQueryBuilder; +import org.opsli.modulars.system.user.entity.SysUser; +import org.opsli.modulars.system.user.service.IUserService; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + + +/** + * @BelongsProject: opsli-boot + * @BelongsPackage: org.opsli.modulars.test.web + * @Author: Parker + * @CreateTime: 2020-09-13 17:40 + * @Description: 用户信息 + */ +@Slf4j +@ApiRestController("/sys/user") +public class UserRestController extends BaseRestController + implements UserApi { + + + /** + * 用户信息 查一条 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "获得单条字典明细数据", notes = "获得单条字典明细数据 - ID") + @Override + public ResultVo get(UserModel model) { + // 如果系统内部调用 则直接查数据库 + if(model != null && model.getIzApi() != null && model.getIzApi()){ + model = IService.get(model); + } + return ResultVo.success(model); + } + + /** + * 用户信息 查询分页 + * @param pageNo 当前页 + * @param pageSize 每页条数 + * @param request request + * @return ResultVo + */ + @ApiOperation(value = "获得分页数据", notes = "获得分页数据 - 查询构造器") + @Override + public ResultVo findPage(Integer pageNo, Integer pageSize, HttpServletRequest request) { + + QueryBuilder queryBuilder = new WebQueryBuilder<>(SysUser.class, request.getParameterMap()); + Page page = new Page<>(pageNo, pageSize); + page.setQueryWrapper(queryBuilder.build()); + page = IService.findPage(page); + + return ResultVo.success(page.getBootstrapData()); + } + + /** + * 用户信息 新增 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "新增字典明细数据", notes = "新增字典明细数据") + @Override + public ResultVo insert(UserModel model) { + // 调用新增方法 + IService.insert(model); + return ResultVo.success("新增字典明细数据成功"); + } + + /** + * 用户信息 修改 + * @param model 模型 + * @return ResultVo + */ + @ApiOperation(value = "修改字典明细数据", notes = "修改字典明细数据") + @Override + public ResultVo update(UserModel model) { + // 调用修改方法 + IService.update(model); + return ResultVo.success("修改字典明细数据成功"); + } + + + /** + * 用户信息 删除 + * @param id ID + * @return ResultVo + */ + @ApiOperation(value = "删除字典明细数据数据", notes = "删除字典明细数据数据") + @Override + public ResultVo del(String id){ + IService.delete(id); + return ResultVo.success("删除字典明细数据成功"); + } + + + /** + * 用户信息 批量删除 + * @param ids ID 数组 + * @return ResultVo + */ + @ApiOperation(value = "批量删除字典明细数据数据", notes = "批量删除字典明细数据数据") + @Override + public ResultVo delAll(String[] ids){ + IService.deleteAll(ids); + return ResultVo.success("批量删除字典明细数据成功"); + } + + + /** + * 用户信息 Excel 导出 + * @param request request + * @param response response + * @return ResultVo + */ + @ApiOperation(value = "导出Excel", notes = "导出Excel") + @Override + public ResultVo exportExcel(HttpServletRequest request, HttpServletResponse response) { + QueryBuilder queryBuilder = new WebQueryBuilder<>(SysUser.class, request.getParameterMap()); + return super.excelExport(UserApi.TITLE, queryBuilder.build(), response); + } + + /** + * 用户信息 Excel 导入 + * @param request 文件流 request + * @return ResultVo + */ + @ApiOperation(value = "导入Excel", notes = "导入Excel") + @Override + public ResultVo excelImport(MultipartHttpServletRequest request) { + return super.excelImport(request); + } + + /** + * 用户信息 Excel 下载导入模版 + * @param response response + * @return ResultVo + */ + @ApiOperation(value = "导出Excel模版", notes = "导出Excel模版") + @Override + public ResultVo importTemplate(HttpServletResponse response) { + return super.importTemplate(UserApi.TITLE, response); + } + + /** + * 根据 username 获得用户 + * @param username 用户名 + * @return ResultVo + */ + @ApiOperation(value = "根据 username 获得用户", notes = "根据 username 获得用户") + @Override + public ResultVo getUserByUsername(String username) { + UserModel userModel = IService.queryByUserName(username); + if(userModel != null){ + return ResultVo.success(userModel); + } + return ResultVo.error("没有该用户"); + } + + /** + * 根据 userId 获得用户角色 + * @param userId 用户Id + * @return ResultVo + */ + @Override + public ResultVo> getRolesByUserId(String userId) { + List roleCodeList = IService.getRoleCodeList(userId); + return ResultVo.success(roleCodeList); + } + + /** + * 根据 userId 获得用户权限 + * @param userId 用户Id + * @return ResultVo + */ + @Override + public ResultVo> getAllPerms(String userId) { + List allPerms = IService.getAllPerms(userId); + return ResultVo.success(allPerms); + } + + + /** + * 根据 userId 获得用户菜单 + * @param userId 用户Id + * @return ResultVo + */ + @Override + public ResultVo> getMenuListByUserId(String userId) { + List menuModelList = IService.getMenuListByUserId(userId); + return ResultVo.success(menuModelList); + } +} diff --git a/opsli-modulars/opsli-modulars-test/src/main/java/org/opsli/modulars/test/service/impl/TestServiceImpl.java b/opsli-modulars/opsli-modulars-test/src/main/java/org/opsli/modulars/test/service/impl/TestServiceImpl.java index 8317739..6b58869 100644 --- a/opsli-modulars/opsli-modulars-test/src/main/java/org/opsli/modulars/test/service/impl/TestServiceImpl.java +++ b/opsli-modulars/opsli-modulars-test/src/main/java/org/opsli/modulars/test/service/impl/TestServiceImpl.java @@ -1,9 +1,9 @@ package org.opsli.modulars.test.service.impl; import org.opsli.api.wrapper.test.TestModel; -import org.opsli.common.annotation.EnableHotData; -import org.opsli.common.annotation.HotDataDel; -import org.opsli.common.annotation.HotDataPut; +import org.opsli.common.annotation.hotdata.EnableHotData; +import org.opsli.common.annotation.hotdata.HotDataDel; +import org.opsli.common.annotation.hotdata.HotDataPut; import org.opsli.core.base.service.impl.CrudServiceImpl; import org.opsli.modulars.test.entity.TestEntity; import org.opsli.modulars.test.mapper.TestMapper; diff --git a/opsli-modulars/opsli-modulars-test/src/main/java/org/opsli/modulars/test/web/TestRestRestController.java b/opsli-modulars/opsli-modulars-test/src/main/java/org/opsli/modulars/test/web/TestRestRestController.java index 7eda5df..dd2c6d7 100644 --- a/opsli-modulars/opsli-modulars-test/src/main/java/org/opsli/modulars/test/web/TestRestRestController.java +++ b/opsli-modulars/opsli-modulars-test/src/main/java/org/opsli/modulars/test/web/TestRestRestController.java @@ -3,17 +3,15 @@ package org.opsli.modulars.test.web; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.RandomUtil; import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.opsli.api.base.result.ResultVo; import org.opsli.api.web.test.TestApi; -import org.opsli.api.wrapper.system.dict.DictModel; +import org.opsli.api.wrapper.system.dict.DictWrapper; import org.opsli.api.wrapper.test.TestModel; import org.opsli.common.annotation.ApiRestController; import org.opsli.common.utils.WrapperUtil; import org.opsli.core.base.concroller.BaseRestController; -import org.opsli.core.cache.local.CacheUtil; import org.opsli.core.cache.pushsub.enums.CacheType; import org.opsli.core.cache.pushsub.msgs.DictMsgFactory; import org.opsli.core.persistence.Page; @@ -84,7 +82,7 @@ public class TestRestRestController extends BaseRestController sendMsg(){ - DictModel model = new DictModel(); + DictWrapper model = new DictWrapper(); BaseSubMessage msg = DictMsgFactory.createMsg(model, CacheType.UPDATE); @@ -302,7 +300,7 @@ public class TestRestRestController extends BaseRestController insertAll(){ - List testType = DictUtil.getDictList("testType"); + List testType = DictUtil.getDictList("testType"); List datas = new ArrayList<>(); // 转化对象 处理 ApiModel 与 本地对象 diff --git a/opsli-plugins/opsli-plugins-redis/src/main/java/org/opsli/plugins/redis/RedisPlugin.java b/opsli-plugins/opsli-plugins-redis/src/main/java/org/opsli/plugins/redis/RedisPlugin.java index 584d92a..e5c0e7d 100644 --- a/opsli-plugins/opsli-plugins-redis/src/main/java/org/opsli/plugins/redis/RedisPlugin.java +++ b/opsli-plugins/opsli-plugins-redis/src/main/java/org/opsli/plugins/redis/RedisPlugin.java @@ -430,9 +430,38 @@ public class RedisPlugin { * @return */ public boolean hPut(String key, String field, Object value) { + return this.hPut(key, field, value, -1, TimeUnit.SECONDS); + } + + /** + * 添加一个Hash 数据 + * @param key + * @param field + * @param value + * @return + */ + public boolean hPut(String key, String field, Object value, long timeout) { + return this.hPut(key, field, value, timeout, TimeUnit.SECONDS); + } + + /** + * 存入普通对象 + * 有时间限制 + * + * @param key Redis键 + * @param field + * @param value 值 + * @param timeout 有效期,单位秒 + * @param unit 时间单位 + * @return boolean + */ + public boolean hPut(String key, String field, Object value, long timeout, TimeUnit unit) { boolean ret = false; try { redisTemplate.opsForHash().put(key, field, value); + if (timeout > 0) { + expire(key, timeout, unit); + } ret = true; } catch (Exception e) { log.error(e.getMessage(),e); diff --git a/opsli-starter/src/main/java/org/opsli/OpsliApplication.java b/opsli-starter/src/main/java/org/opsli/OpsliApplication.java index 2aba19f..2047c36 100644 --- a/opsli-starter/src/main/java/org/opsli/OpsliApplication.java +++ b/opsli-starter/src/main/java/org/opsli/OpsliApplication.java @@ -1,19 +1,14 @@ package org.opsli; -import org.opsli.general.StartPrint; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.ConfigurableApplicationContext; -import springfox.documentation.swagger2.annotations.EnableSwagger2; @SpringBootApplication() public class OpsliApplication { public static void main(String[] args){ - ConfigurableApplicationContext application = SpringApplication.run(OpsliApplication.class, args); - // 打印启动日志 - StartPrint.INSTANCE.print(application.getEnvironment()); + SpringApplication.run(OpsliApplication.class, args); } } \ No newline at end of file diff --git a/opsli-starter/src/main/resources/application.yaml b/opsli-starter/src/main/resources/application.yaml index 0769581..aff526c 100644 --- a/opsli-starter/src/main/resources/application.yaml +++ b/opsli-starter/src/main/resources/application.yaml @@ -44,6 +44,9 @@ spring: autoconfigure: exclude: - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure + # + #main: + # allow-bean-definition-overriding: true # 邮件设置 mail: host: smtp.qq.com @@ -142,3 +145,8 @@ mybatis-plus: call-setters-on-nulls: true # 打印SQL 开发测试使用 生产关闭 *** log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +# opsli 自定义配置 +opsli: + # token 有效时间 (分钟) 2小时 + token-effective-time: 120 diff --git a/pom.xml b/pom.xml index 6095b3b..16fa89f 100644 --- a/pom.xml +++ b/pom.xml @@ -68,20 +68,79 @@ 2.3.0.RELEASE 1.2.73 3.4.0 + 1.3.0 1.1.17 2.5.4 - 0.9.1 + 3.10.3 3.11 2.8.0 29.0-jre 5.4.2 3.9.0 + 0.0.9 + 1.6.0 + 3.1.0 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + com.github.axet + kaptcha + ${kaptcha.version} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + com.auth0 + java-jwt + ${jwt.version} + + + + + @@ -114,27 +173,6 @@ --> - - - - @@ -148,14 +186,7 @@ com.github.pagehelper pagehelper-spring-boot-starter - 1.3.0 - - - - - - - + ${pagehelper.version} @@ -227,17 +258,13 @@ ${guava.version} - - - org.projectlombok lombok - - + com.alibaba fastjson @@ -254,18 +281,7 @@ - - - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - +