Daily-Blog项目后台日志(上)

简介: Daily-Blog项目后台日志(上)

博客后台



image-20230328200728728.png


AOP实现日志记录


需求


通过日志记录接口调用信息,便于后期排查


格式如下 :


image-20230320163423192.pngimage-20230320163644874.png



实现


1.先定义注解类

/**
 * 自定义注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SystemLog {
    String businessName();
}


2.定义切面类


/**
 * 切面类
 */
@Component
@Aspect
@Slf4j
public class LogAspect {
}


3.定义切点,及其通知方法


@Component
@Aspect
@Slf4j
public class LogAspect {
    //确定切点
    @Pointcut("@annotation(com.blog.annotation.SystemLog)")
    public void pt(){
    }
    //通知方法(使用环绕通知)
    @Around("pt()")
    public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
        joinPoint.getArgs();
        Object res; //得到目标方法调用的返回值
        try {
            handleBefore(joinPoint);
            //目标方法的调用
            res = joinPoint.proceed();
            //打印响应信息
            handleAfter(res);
        }//无论有没有异常都需要打印异常信息
        finally {
            //换行
            log.info("============End============" + System.lineSeparator());
        }
        return res;
    }
    private void handleBefore(ProceedingJoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //获取被增强方法上的注解对象
        SystemLog systemLog = getSystemLog(joinPoint);
        log.info("============Start============");
        // 打印请求 URL
        log.info("URL            : {}",request.getRequestURL());
        // 打印描述信息
        log.info("BusinessName   : {}",systemLog.businessName());
        // 打印 Http method
        log.info("HTTP Method    : {}",request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}",joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());
        // 打印请求的 IP
        log.info("IP             : {}",request.getRemoteHost());
        // 打印请求入参
        log.info("Request Args   : {}", JSON.toJSONString(joinPoint.getArgs()) );
    }
    private void handleAfter(Object res) {
        // 打印出参
        log.info("Response       : {}", JSON.toJSONString(res));
    }
    //todo 获取被增强方法上的注解对象
    private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        SystemLog systemLog = methodSignature.getMethod().getAnnotation(SystemLog.class);
        return systemLog;
    }
}

4.在需要增强的方法上添加自定义注解


@SystemLog(businessName="更新用户信息")
@GetMapping("/userInfo")
public ResponseResult userInfo(){
    return userService.userInfo();
}


Swagger2


依赖


<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
</dependency>

image-20230321215827890.png

基本使用


@ApiOperation(value = "友链评论列表",notes = "获取一页友链评论")
@GetMapping("/linkCommentList")
public ResponseResult listCommentList(Integer pageNum , Integer pageSize){
    return commentService.commentList(SystemConstants.COMMENT_TYPE_FRIEND,null,pageNum,pageSize);
}

使用@ApiOperation(value = "友链评论列表",notes = "获取一页友链评论")来进行标注


配置形参


@ApiImplicitParams({
        @ApiImplicitParam(name = "pageNum", value = "页号"),
        @ApiImplicitParam(name = "pageSize", value = "每页大小")
})


image-20230322163816207.pngimage-20230322164820155.png


**实体类接口 : **


一般一个实体类不止在一个接口中被用到,所以如果直接在实体类中添加的话就是使代码耦合,所以我们需要进行拆解


@ApiModel(description = "文章实体类")
public class Article{
}

所以上面的写法是不正规的


所以我们需要使用DTO对象


【dto对象 :数据传输对象】


按照开发规范,所有的controller层需要的实体类参数,我们都需要将其转换为dto对象


//todo 添加评论
@PostMapping
public ResponseResult addComment(@RequestBody AddCommentDto addCommentDto){
    Comment comment = BeanCopyUtils.copyBean(addCommentDto, Comment.class);
    return commentService.addComment(comment);
}
@ApiModel(description = "添加评论实体类")
public class AddCommentDto {
    private Long id;
    //评论类型(0代表文章评论,1代表友链评论)
    private String type;
    //文章id
    @ApiModelProperty(notes = "文章id")
    private Long articleId;
    //根评论id
  //......   
}

所有的dto都是需要添加的

image-20230322170449009.png


获取所有标签

接口

image-20230323184335879.png


实现

/**
 * 标签请求
 */
@RestController
@RequestMapping("/content/tag")
public class TagController {
    @Resource
    private TagService tagService;
    @GetMapping("/list")
    public ResponseResult list(){
        return ResponseResult.okResult(tagService.list());
    }
}


后台登录、登出


接口

image-20230323184730929.png


登录


①自定义登录接口


调用ProviderManager的方法进行认证 如果认证成功生成jwt


把信息存入redis中


②自定义UserDetailsServic e


在这个实现类中进行查询数据库操作


注意配置密码加密BCryptPasswordCoder


校验


①自定义jwt认证过滤器


获取token


解析token获取其中的userId


从redis中获取用户信息


存入securityContextHolder

image-20230323190712580.png


实现


@RestController
@RequestMapping("/user")
public class loginController {
    @Resource
    private AdminLoginService loginService;
    @PostMapping("/login")
    public ResponseResult login(@RequestBody User user){
        if (!StringUtils.hasText(user.getUserName())){
            //提示 要传用户名
            throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
        }
        return loginService.login(user);
    }
    @PostMapping("/logout")
    public ResponseResult logout(){
        return loginService.logout();
    }
}


/**
 * 后台登陆实现
 */
@Service
public class AdminLoginServiceImpl implements AdminLoginService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Resource
    private RedisCache redisCache;
    //todo 登录业务
    @Override
    public ResponseResult login(User user) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //判断是否认证通过
        //获取userId ,生成token
        //判断是否认证通过
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("用户名或密码错误");
        }
        //获取userid 生成token
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        //把用户信息存入redis
        redisCache.setCacheObject(SystemConstants.LOGIN_KEY + userId,loginUser);
        //封装响应  : 把token 和userInfoVo(由user转换而成) 封装 ,然后返回
        Map<String,String> map = new HashMap<>();
        map.put("token",jwt);
        return ResponseResult.okResult(map);
    }
    @Override
    public ResponseResult logout() {
        //获取 token 解析获取 userId
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        Long userId = loginUser.getUser().getId();
        redisCache.deleteObject(SystemConstants.LOGIN_KEY + userId);
        return ResponseResult.okResult();
    }
}


后台权限控制及其动态路由


表分析

权限表image-20230323193145026.png


对应的页面image-20230323193349478.png


权限表


image-20230323194436827.png

角色权限表

image-20230323194501433.png


获取当前用户的权限和角色信息


接口(getInfo)


image-20230323194702962.pngimage-20230323195059120.png



实现getInfo


最终实现结果

{
  "code": 200,
  "data": {
  "permissions": [
    "system:menu:list",
    "system:menu:query",
    "system:menu:add",
    "system:menu:edit",
    "system:menu:remove",
    "content:article:writer"
  ],
  "roles": [
    "common",
    "link"
  ],
  "user": {
    "avatar": "http://rrpanx30j.hd-bkt.clouddn.com/images/91529822720e0cf3efed815e0446f21fbe09aa79.png",
    "email": "23412532@qq.com",
    "id": 4,
    "nickName": "红红火火恍恍惚惚",
    "sex": "1"
  }
  },
  "msg": "操作成功"
}
controller


@RestController
public class UserController {
    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;
    @GetMapping("/getInfo")
    public ResponseResult<AdminUserInfoVo> getInfo(){
        //1. 查询当前登陆的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        //2. 根据用户id查询权限
        List<String> perms = menuService.selectPermsByUserId(loginUser.getUser().getId());
        // 根据id查询角色信息
        List<String> roleKeyList = roleService.selectRoleKeyByUserId(loginUser.getUser().getId());
        //3. 封装 返回
        User user = loginUser.getUser();
        UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
        AdminUserInfoVo adminUserInfoVo = new AdminUserInfoVo(perms,roleKeyList,userInfoVo);
        return ResponseResult.okResult(adminUserInfoVo);
    }
}
service层两个实现类
/**
 * 查询角色权限信息
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
    /**
     * 根据用户id查询权限信息<br>
     * 如果用户id为1 代表管理员 ,menus中需要有所有菜单类型为c或者F的,状态为,未被删除的权限
     * @param id 用户id
     * @return 返回该用户权限集合
     */
    @Override
    public List<String> selectPermsByUserId(Long id) {
        //管理员返回所有的权限
        if(id == 1){
            LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
            wrapper.in(Menu::getMenuType, SystemConstants.MENU_TYPE_C,SystemConstants.MENU_TYPE_F);  //菜单类型为C 和 F
            wrapper.eq(Menu::getStatus,SystemConstants.LINK_STATUS_NORMAL);//状态正常
            List<Menu> menus = list(wrapper);
            List<String> Perms = menus.stream().map(Menu::getPerms).collect(Collectors.toList());
            return Perms;
        }
        // 反之返回相对应用户所具有的权限
        //1. 先查询sys_user_roles查询用户角色id
        //2. 查到角色id之后再到sys_roles_menu查询对应的权限id(menuId)
        //3. 最后通过menuId查询对应的menu信息
        //4. 封装返回
        return getBaseMapper().selectPermsByUserId(id);
    }
}


/**
 * 查询角色信息
 */
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
    @Override
    public List<String> selectRoleKeyByUserId(Long id) {
        //判断是否为管理员角色
        if(id == 1){
            List<String> roleKeys = new ArrayList<>();
            roleKeys.add("admin");  //管理员角色
            return roleKeys;
        }
        //如果不是返回对应id的角色信息(连表查询)
        return getBaseMapper().selectRoleKeyByUserId(id);
    }
}

对应多表联查的xml文件


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.blog.mapper.RoleMapper">
<!--            selectRoleKeyByUserId-->
    <select id="selectRoleKeyByUserId" resultType="java.lang.String">
        SELECT
            r.`role_key`
        FROM
            `sys_user_role` ur
                LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
        WHERE
            ur.`user_id` = #{userId} AND
            r.`status` = 0 AND
            r.`del_flag` = 0
    </select>
    <select id="selectRoleIdByUserId" resultType="java.lang.Long">
        select r.id
        from sys_role r
                 left join sys_user_role ur on ur.role_id = r.id
        where ur.user_id = #{userId}
    </select>
</mapper>


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.blog.mapper.MenuMapper">
    <select id="selectPermsByUserId" resultType="java.lang.String">
        SELECT
            DISTINCT m.perms
        FROM
            `sys_user_role` ur
                LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
                LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
        WHERE
            ur.`user_id` = #{userId} AND
            m.`menu_type` IN ('C','F') AND
            m.`status` = 0 AND
            m.`del_flag` = 0
    </select>
    <select id="selectAllRouterMenu" resultType="com.blog.domain.entity.Menu">
        SELECT
            DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, IFNULL(m.perms,'') AS perms, m.is_frame,  m.menu_type, m.icon, m.order_num, m.create_time
        FROM
            `sys_menu` m
        WHERE
            m.`menu_type` IN ('C','M') AND
            m.`status` = 0 AND
            m.`del_flag` = 0
        ORDER BY
            m.parent_id,m.order_num
    </select>
    <select id="selectRouterMenuTreeByUserId" resultType="com.blog.domain.entity.Menu">
        SELECT
            DISTINCT m.id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, IFNULL(m.perms,'') AS perms, m.is_frame,  m.menu_type, m.icon, m.order_num, m.create_time
        FROM
            `sys_user_role` ur
                LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
                LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
        WHERE
            ur.`user_id` = #{userId} AND
            m.`menu_type` IN ('C','M') AND
            m.`status` = 0 AND
            m.`del_flag` = 0
        ORDER BY
            m.parent_id,m.order_num
    </select>
    <select id="selectMenuListByRoleId" resultType="java.lang.Long">
        select m.id
        from sys_menu m
                 left join sys_role_menu rm on m.id = rm.menu_id
        where rm.role_id = #{roleId}
        order by m.parent_id, m.order_num
    </select>
</mapper>


动态路由接口(getRouters)

image-20230323195215441.png


响应格式


前端为了实现动态路由的效果,需要后端有接口能够返回所有的菜单数据


注意 :返回的菜单需要体现父子菜单的层级关系


如果用户id为1 代表管理员 ,menus中需要有所有菜单类型为c或者M的,状态为,未被删除的权限

image-20230323195723943.png


{
  "code": 200,
  "data": {
  "menus": [
    {
    "children": [],
    "component": "content/article/write/index",
    "createTime": "2022-01-08 03:39:58",
    "icon": "build",
    "id": 2023,
    "isFrame": 1,
    "menuName": "写博文",
    "menuType": "C",
    "orderNum": 0,
    "parentId": 0,
    "path": "write",
    "perms": "content:article:writer",
    "status": "0",
    "visible": "0"
    },
    {
    "children": [
      {
      "children": [],
      "component": "system/menu/index",
      "createTime": "2021-11-12 10:46:19",
      "icon": "tree-table",
      "id": 102,
      "isFrame": 1,
      "menuName": "菜单管理",
      "menuType": "C",
      "orderNum": 3,
      "parentId": 1,
      "path": "menu",
      "perms": "system:menu:list",
      "status": "0",
      "visible": "0"
      }
    ],
    "createTime": "2021-11-12 10:46:19",
    "icon": "system",
    "id": 1,
    "isFrame": 1,
    "menuName": "系统管理",
    "menuType": "M",
    "orderNum": 1,
    "parentId": 0,
    "path": "system",
    "perms": "",
    "status": "0",
    "visible": "0"
    }
  ]
  },
  "msg": "操作成功"
}


实现getRouters


controller


@GetMapping("/getRouters")
public ResponseResult<RoutersVo> getRouters(){
    Long userId = SecurityUtils.getUserId();
    //查询menu 结果是tree形状
    List<Menu> menus = menuService.selectRouterMenuTreeByUserId(userId);
    RoutersVo routersVo = new RoutersVo(menus);
    //封装返回
    return ResponseResult.okResult(routersVo);
}


service层实现


@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {  
  /**
     * 根据用户id查询相关的权限菜单信息
     * @param userId 用户id
     * @return 返回符合要求的val
     */
    @Override
    public List<Menu> selectRouterMenuTreeByUserId(Long userId) {
        MenuMapper menuMapper = getBaseMapper();
        List<Menu> menus = null;    //封装Menu
        //如果是管理员,返回所有的菜单
        if(SecurityUtils.isAdmin()){
            menus = menuMapper.selectAllRouterMenu();
        }
        else{
            //如果不是管理员 那么返回对应有权限的菜单按钮
            menus = menuMapper.selectRouterMenuTreeByUserId(userId);
        }
        //通过上述得到的Menu无法得到我们需要的父子菜单管理,所以我们需要通过(buildMenuTree)来构建这种父子菜单关系
        //构建Tree
        //先找出第一层的菜单,接着找到他们的子菜单,然后就可以设置children属性中
        List<Menu> menuTree = buildMenuTree(menus,0L);
        return menuTree;
    }
    /**
     * 构建菜单的父子菜单关系
     * <br>
     * 找到父menu对应的子menu ,然后将他们放到一个集合中,最后设置给children这个字段
     * @param menus 传入的方法
     * @param parentId 父菜单id
     * @return
     */
    private List<Menu> buildMenuTree(List<Menu> menus, Long parentId) {
        List<Menu> menuList = menus.stream()//通过这样筛选就可以得到第一层级的menu
                .filter(menu -> menu.getParentId().equals(parentId))
                /*
                传入的menus是得到了第一层的menus(相当于Tree中的root节点),然后需要设置他的子菜单(left 和 right)
                因为menus中有所有的菜单(父子都有), 所以我们在设置left和right时需要找到他们的子菜单
                所以就调用getChildren找到left或者right的子菜单,然后得到之后再设置给他们
                 */
                .map(menu -> menu.setChildren(getChildren(menu, menus)))
                .collect(Collectors.toList());
        return menuList;
    }
    /**
     * 获取传入参数的子menu的list集合
     *  在menus中找打当前传入的menu的子菜单
     * @param menu
     * @param menus
     */
    private List<Menu> getChildren(Menu menu, List<Menu> menus){
        List<Menu> children = menus.stream()
                .filter(menu1 -> menu1.getParentId().equals(menu.getId()))
                .map(menu1 -> menu1.setChildren(getChildren(menu1,menus)))  //如果有很多的子菜单,那么就可以用到这个递归
                .collect(Collectors.toList());
        return children;
    }
}


相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录——使用Logger在项目中打印日志
本文介绍了如何在项目中使用Logger打印日志。通过SLF4J和Logback,可设置不同日志级别(如DEBUG、INFO、WARN、ERROR)并支持占位符输出动态信息。示例代码展示了日志在控制器中的应用,说明了日志配置对问题排查的重要性。附课程源码下载链接供实践参考。
1324 0
|
11月前
|
Java 应用服务中间件 Linux
Tomcat运行日志字符错乱/项目启动时控制台日志乱码问题
总结: 通过以上几种方法,概括如下:指定编码格式、设置JVM的文件编码、修改控制台输出编码、修正JSP页面编码和设置过滤器。遵循这些步骤,你可以依次排查和解决Tomcat运行日志字符错乱及项目启动时控制台日志乱码问题。希望这些建议能对你的问题提供有效的解决方案。
1997 16
|
人工智能 监控 算法
3D-Speaker:阿里通义开源的多模态说话人识别项目,支持说话人识别、语种识别、多模态识别、说话人重叠检测和日志记录
3D-Speaker是阿里巴巴通义实验室推出的多模态说话人识别开源项目,结合声学、语义和视觉信息,提供高精度的说话人识别和语种识别功能。项目包含工业级模型、训练和推理代码,以及大规模多设备、多距离、多方言的数据集,适用于多种应用场景。
4048 18
3D-Speaker:阿里通义开源的多模态说话人识别项目,支持说话人识别、语种识别、多模态识别、说话人重叠检测和日志记录
|
Java Maven
java项目中jar启动执行日志报错:no main manifest attribute, in /www/wwwroot/snow-server/z-server.jar-jar打包的大小明显小于正常大小如何解决
在Java项目中,启动jar包时遇到“no main manifest attribute”错误,且打包大小明显偏小。常见原因包括:1) Maven配置中跳过主程序打包;2) 缺少Manifest文件或Main-Class属性。解决方案如下:
3114 8
java项目中jar启动执行日志报错:no main manifest attribute, in /www/wwwroot/snow-server/z-server.jar-jar打包的大小明显小于正常大小如何解决
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
377 1
|
开发框架 .NET Docker
【Azure 应用服务】App Service .NET Core项目在Program.cs中自定义添加的logger.LogInformation,部署到App Service上后日志不显示Log Stream中的问题
【Azure 应用服务】App Service .NET Core项目在Program.cs中自定义添加的logger.LogInformation,部署到App Service上后日志不显示Log Stream中的问题
243 1
|
数据可视化 Java API
如何在项目中快速引入Logback日志并搭配ELK使用
如何在项目中快速引入Logback日志并搭配ELK使用
|
监控 安全 Apache
什么是Apache日志?为什么Apache日志分析很重要?
Apache是全球广泛使用的Web服务器软件,支持超过30%的活跃网站。它通过接收和处理HTTP请求,与后端服务器通信,返回响应并记录日志,确保网页请求的快速准确处理。Apache日志分为访问日志和错误日志,对提升用户体验、保障安全及优化性能至关重要。EventLog Analyzer等工具可有效管理和分析这些日志,增强Web服务的安全性和可靠性。
511 9
|
11月前
|
监控 容灾 算法
阿里云 SLS 多云日志接入最佳实践:链路、成本与高可用性优化
本文探讨了如何高效、经济且可靠地将海外应用与基础设施日志统一采集至阿里云日志服务(SLS),解决全球化业务扩展中的关键挑战。重点介绍了高性能日志采集Agent(iLogtail/LoongCollector)在海外场景的应用,推荐使用LoongCollector以获得更优的稳定性和网络容错能力。同时分析了多种网络接入方案,包括公网直连、全球加速优化、阿里云内网及专线/CEN/VPN接入等,并提供了成本优化策略和多目标发送配置指导,帮助企业构建稳定、低成本、高可用的全球日志系统。
1051 54