day08
);```#### 2、CategoryServiceImpl```java@Overridepublic boolean cateExist(Category category)
{ /* id已存在 或者 parent_id下 该名称的分类已存在 select count(1) from categ
ory where del_flag = 0 and id = #{category.id} or ( parent_id = #{category.
parent_id} and name = #{category.name}) LambdaQueryWrapper: 组装复杂的sql */
LambdaQueryWrapper<Category> queryWrapper = Wrappers.lambdaQuery(Category.class); /
/QueryWrapper 默认使用and的方式连接多个条件 queryWrapper.eq(Category::getId , category.getId());
//如果使用or连接的多个条件 需要作为整体 使用and连接,可以在or()方法内组装条件 //如果使用or连接的多个条件 不是整体 可以在or()方法后组装条件 q
ueryWrapper.or(q-> q.eq(Category::getParentId , category.getParentId())
.eq(Category::getName , category.getName()) ); return baseMapper.sel
ectCount(queryWrapper) > 0 ;}```#### 3、CategoryExcelDataReadListener```java@Slf4jpublic cl
ass CategoryExcelDataReadListener extends AnalysisEventListener<Category> { private Lis
t<Category> categories = new ArrayList<>(); private int limit = 10; // @Resource
CategoryService categoryService; public CategoryExcelDataReadListener(CategoryServic
e categoryService) { this.categoryService = categoryService; } @Transactional
(rollbackFor = Exception.class) @Override public void invoke(Category data, Analysis
Context context) { /* 校验读取到的数据 是不是已存在 id已存在 或者 parent_id
下 该名称的分类已存在 */ if(categoryService.cateExist(data)){ //如果读取到的一行分类
数据 已存在 继续读取下一条 //不存在 加入到批量保存的集合中 log.info("分类:{} 数据已存在" , JSON.toJSO
NString(data)); return; } categories.add(data); //判断是否触发阈值
批量保存 if(categories.size()>=limit){ categoryService.saveBatch(categories
); categories.clear(); } } @Transactional(rollbackFor = Exception.
class) @Override public void doAfterAllAnalysed(AnalysisContext context) { if
(categories.size()>0 ){ categoryService.saveBatch(categories); categ
ories.clear(); } }}```### 1.2 前端views/product/brand/index.vue```vue```### 1.3 前端
分页导航条展示异常解决ctrl+shift+R搜索: searchObj.value.total = total 替换为: searchObj.value.total = +to
tal## 2、权限管理### 2.1 常见的权限管理模型权限管理: 一般指的是后台管理系统 控制管理员权限(不同模块 不同的操作的权限 CRUD)#### 1、aclaccess
control 访问权限控制小型系统使用的权限控制模型 管理员不多```permission表: 权限表1 brand:list2 brand:delete3 brand:e
dit4 brand:saveuser表: 管理员表1 zhangsanuser_permission表: 用户权限中间表 用户和权限是多对多的关系user_id permis
sion_id1 11 4管理员 zhangsan登录时,可以查询 zhangsan的用户信息和他的权限列表,缓存到redis中{name: zhangsanid:
1permissions: ["brand:list","brand:save"]}zhangsan从前端提交 CRUD的请求时,后端可以根据登录的token 从redis'缓存
中获取它的permissions列表> 访问的接口 可以添加权限要求,通过aop(切面)接口业务执行前,可以通过切面类通知 检查接口要求的权限字符串 该用户pemissions列表
中是否包含```#### 2、rbacrole based access control 基于角色的访问控制(通过角色对权限进行了分组管理 将一组权限授权给一个角色,用户可以绑定
角色或者权限)```permission表: 权限表1 brand:list2 brand:delete3 brand:edit4 brand:saverole表: 角色表1 ad
min2 linshiadminrole_permission表: 角色权限表 角色权限 多对多role_id perssmion_id1 11 21 31 42
1user表: 管理员表1 zhangsanuser_role表: 用户角色中间表 用户和角色是多对多的关系user_id role_id1 2用户登录时,可以
查询用户的角色和权限列表{name: zhangsanid: 1permissions: ["brand:list"],roles:["linshiadmin"]}接口方法上可以要
求权限或者角色:```### 2.2 aop#### 1、若依后端权限控制流程```1、管理员登录成功后,访问页面前会先查询他的所有权限和角色如果他是admin管理员,权限为: *
:*:*> spzx-system: 权限查询 相关的表sys_usersys_user_rolesys_rolesys_role_menusys_menu> 返回的数据中就包含了
用户信息+ 管理员权限 角色列表"permissions": [ "*:*:*" ], "roles": [ "admin" ],
"user": { }2、管理员提交CRUD的请求,后端鉴权> 2.1 前端的权限控制用户只能看到自己有权操作的数据 或者 菜单前端检查权限: v-hasPe
rmi="['system:menu:add']" v-hasPermi="['system:role:add']"> 2.2 以新增角色为例1、新创建角色:角色只有查询角色列表的
权限2、新创建用户:给用户分配上面创建的权限3、前端角色页面中:删除新增角色按钮的权限要求4、后端在新增角色的接口上添加注解,要求权限检查:@RequiresPermissions
("system:role:add")注解本身没有作用,spring中通过aop给注解赋能5、鉴权的切面类:spzx-common-security: PreAuthorizeA
spect源码参考下面```--- 鉴权切面源码:```java/** * 定义AOP签名 (切入所有使用鉴权注解的方法) 切入点表达式 */public stat
ic final String POINTCUT_SIGN = " @annotation(com.spzx.common.security.annotation.Requires
Login) || " + "@annotation(com.spzx.common.security.annotation.RequiresPermissions) ||
" + "@annotation(com.spzx.common.security.annotation.RequiresRoles)";/** * 声明AOP签名
将表达式和一个方法绑定,便于以后复用切入点表达式 */@Pointcut(POINTCUT_SIGN)public void pointcut(){}/**
* 环绕切入 * * @param joinPoint 切面对象 * @return 底层方法执行后的返回值 * @throws Throwab
le 底层方法抛出的异常 pointcut() : 复用方法绑定的切入点表达式 */@Around("pointcut()")public Object aroun
d(ProceedingJoinPoint joinPoint) throws Throwable{ MethodSignature signature = (MethodS
ignature) joinPoint.getSignature(); // 注解鉴权: 传入目标方法对象 checkMethodAnnotation(signatur
e.getMethod()); try { // 执行原有逻辑 Object obj = joinPoint.proceed();
return obj; } catch (Throwable e) { throw e; }}/** * 对一个Method对象
进行注解检查 */public void checkMethodAnnotation(Method method){ // 校验 @RequiresLogin 注解
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class); if (require
sLogin != null) { //对使用了RequiresLogin注解的接口方法 进行登录鉴权 //如果请求头没有携带 token,或者携
带的token解析失败,或者token解析不到user对象 直接抛出异常 AuthUtil.checkLogin();//已登录 校验通过 继续向后执行 }
// 校验 @RequiresRoles 注解 RequiresRoles requiresRoles = method.getAnnotation(RequiresRo
les.class); if (requiresRoles != null) { AuthUtil.checkRole(requiresRoles);
} // 校验 @RequiresPermissions 注解 RequiresPermissions requiresPermissions = method.g
etAnnotation(RequiresPermissions.class); if (requiresPermissions != null) { A
uthUtil.checkPermi(requiresPermissions); }}```AuthUtil```java//perssmion鉴权public static
void checkPermi(RequiresPermissions requiresPermissions){ authLogic.checkPermi(require
sPermissions);}public void checkPermi(RequiresPermissions requiresPermissions){ Securit
yContextHolder.setPermission(StringUtils.join(requiresPermissions.value(), ",")); if (r
equiresPermissions.logical() == Logical.AND) { checkPermiAnd(requiresPermissions
.value()); } else { checkPermiOr(requiresPermissions.value()); }}public
void checkPermiAnd(String... permissions){ Set<String> permissionList = getPermiList()
;//获取登录用户的权限列表 for (String permission : permissions)//遍历接口上注解要求的权限列表 { if (!h
asPermi(permissionList, permission))//只要有任意一个权限 用户没有 抛出异常 //超级管理员 权限字符串为 *:*:*
{ throw new NotPermissionException(permission); } }}public vo
id checkPermiOr(String... permissions){ Set<String> permissionList = getPermiList();
for (String permission : permissions) { if (hasPermi(permissionList, permission
)) { return; } } if (permissions.length > 0) { th
row new NotPermissionException(permissions); }}//角色鉴权的业务:public static void checkRole(R
equiresRoles requiresRoles){ authLogic.checkRole(requiresRoles);//requiresRoles表示接口要求的角
色注解对象}public void checkRole(RequiresRoles requiresRoles){ if (requiresRoles.logical() =
= Logical.AND) //获取角色注解对象的 logical属性值,and或者or { //注解中要求的多个角色 必须同时拥有 check
RoleAnd(requiresRoles.value()); } else { //注解中的多个角色 有一个即可 checkRole
Or(requiresRoles.value()); }}public void checkRoleAnd(String... roles){ Set<String>
roleList = getRoleList();//获取登录用户的角色列表 for (String role : roles) //遍历接口要求的角色列表 {
if (!hasRole(roleList, role))//检查用户的角色列表中有任意一个不包含接口要求的角色 抛出异常 //如果用户角色是admi
n 所有接口都可以访问 { throw new NotRoleException(role); } }}public vo
id checkRoleOr(String... roles){ Set<String> roleList = getRoleList(); for (String r
ole : roles) { if (hasRole(roleList, role))//用户有任意一个接口要求的角色 直接解析校验 继续执行 {
return; } } if (roles.length > 0) { throw new NotRoleEx
ception(roles); }}``````若依权限控制:1、前端访问页面前 会加载用户信息和他的角色以及权限列表2、前端渲染页面时: 可以通过 v-hasPermi
v-hasRole... 校验加载的角色和权限列表 如果包含则展示标签 否则不展示3、后端接收到用户的CRUD请求时,如果接口方法上加了 RequiresRoles Req
uiresPermissions 注解 会被PreAuthorizeAspect的环绕通知 进行动态代理增强> 对用户的登录状态 角色 权限 按照注解的要求进行检查本质 就是字符串
比较若依菜单管理:目录菜单: 项目:模块:list按钮: 项目:模块:delete 项目:模块:edit 项目:模块:save```### 2.3 自定义aop打印接口访
问日志#### 1、自定义注解```SpzxPrintLogmodule : 模块名称isPrintRequestParam: 是否打印请求参数isPrintResult : 是否
打印响应结果```#### 2、切面类对自定义注解进行增强(通过环绕通知处理)#### 3、使用在需要打印日志的接口上使用自定义注解```接口执行的时长,所属模块名称,打印请求参数
,返回值```### 2.4 使用若依的权限控制#### 1、超级管理员给普通角色分配权限#### 2、接口上使用权限注解以BrandController为例:```java@Op
eration(summary = "批量删除品牌")@RequiresPermissions(value = {"product:brand:remove"})@Log(titl
e = "删除品牌" ,businessType = BusinessType.DELETE )@DeleteMapping("{ids}")public AjaxResult l
ist(@PathVariable("ids") List<Long> ids) {```#### 3、前端权限校验以brand/index.vue为例```vue<el-butt
on size="small" type="danger" @click="handleDelete(scope.row
)" v-hasPermi="['product:brand:remove']" > 删除</el-button>```### 2.5
若依操作日志记录### 2.6 任务:断点查看若依记录操作日志的流程## 3、spzx-gen代码生成器的使用### 3.1 修改nacos配置naocs配置中心 spzx-g
en-dev.yml 数据库连接修改为 spzx-user账号密码修改为 spzx-user数据库的账号密码逆向工程包名修改为 com.spzx.user```yaml```###
3.2 spzx-user库创建逆向工程表```将gen_table和gen_table_column这两张表加入spzx-user库,存在就忽略```### 3.3 逆向工程后
天管理系统 系统工具--->代码生成 可以选择导入表之后 点击生成## 4、spzx-user服务搭建### 4.1 创建项目**在spzx-modules下创建spzx-us
er****pom文件引入依赖:拷贝spzx-product的****bootstrap.yml**```yml```### 4.2 nacos配置文件克隆 spzx-produc
t-dev 修改为 spzx-user-dev修改spzx-user-dev中连接的数据库名称 为spzx-user### 4.3 启动类```java@SpringBootAp
plication@EnableCustomConfig@EnableRyFeignClientspublic class SpzxUserApplication { pub
lic static void main(String[] args) { SpringApplication.run(SpzxUserApplication.cla
ss, args); }}```### 4.4 将逆向工程的类和mapper导入到项目中### 4.5 修改UserInfo添加扩展字段: 接收前端查询请求以及 用户地址列表
```java@Schema(description = "注册开始时间")@TableField(exist = false)@ExcelIgnoreprivate String
begin;@Schema(description = "注册截至时间")@ExcelIgnore@TableField(exist = false)private String
end;@ExcelIgnore@TableField(exist = false)private List<UserAddress> userAddressList;```##
# 4.6 UserInfoController```java@Operation(summary = "获取会员详细信息")@RequiresPermissions("user:
userInfo:query")@GetMapping(value = "/{id}")public AjaxResult getInfo(@PathVariable("id")
Long id){ UserInfo userInfo = userInfoService.getUserInfoAndAddressedById(id); retur
n success(userInfo);}```### 4.7 UserInfoService``` UserInfo getUserInfoAndAddressedById
(Long id);```### 4.8 UserInfoServiceImpl```java@Overridepublic UserInfo getUserInfoAndAddr
essedById(Long id) { UserInfo userInfo = this.getById(id); List<UserAddress> userAdd
resses = userAddressMapper.selectList(Wrappers.lambdaQuery(UserAddress.class)
.eq(UserAddress::getUserId, id));
userInfo.setUserAddressList(userAddresses); return userInfo;}```### 4.9 UserInfoMapper
.xml按照前端传入的条件查询```xml```### 4.10 前端#### 1、创建菜单路由#### 2、导入api#### 3、导入页面#### 4、修改页面