若依后端框架代码评审文档

若依后端框架代码评审文档

1. 选择的代码文件和测试依据

1.选择的代码文件

  • SysProfileController:

    • 路径: src/com.ruoyi.web.controller.system
  • BaseController:

    • 路径: src/com.ruoyi.web.controller.common
  • CaptchaController:

    • 路径: src/com.ruoyi.common.core.controller

2.测试依据

依据一般后端框架的 Java 和 Spring Boot 的代码规范进行测试。

2. 测试点

2.1 SysProfileController

源码:


package com.ruoyi.web.controller.system;

import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;

/**
 * 个人信息 业务处理
 * 
 * @author ruoyi
 */
@RestController
@RequestMapping("/system/user/profile")
public class SysProfileController extends BaseController
{
    @Autowired
    private ISysUserService userService;

    @Autowired
    private TokenService tokenService;

    /**
     * 个人信息
     */
    @GetMapping
    public AjaxResult profile()
    {
        LoginUser loginUser = getLoginUser();
        SysUser user = loginUser.getUser();
        AjaxResult ajax = AjaxResult.success(user);
        ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername()));
        ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername()));
        return ajax;
    }

    /**
     * 修改用户
     */
    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult updateProfile(@RequestBody SysUser user)
    {
        if (StringUtils.isNotEmpty(user.getPhonenumber())
                && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
        {
            return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
        }
        if (StringUtils.isNotEmpty(user.getEmail())
                && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
        {
            return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
        }
        LoginUser loginUser = getLoginUser();
        SysUser sysUser = loginUser.getUser();
        user.setUserId(sysUser.getUserId());
        user.setPassword(null);
        if (userService.updateUserProfile(user) > 0)
        {
            // 更新缓存用户信息
            sysUser.setNickName(user.getNickName());
            sysUser.setPhonenumber(user.getPhonenumber());
            sysUser.setEmail(user.getEmail());
            sysUser.setSex(user.getSex());
            tokenService.setLoginUser(loginUser);
            return AjaxResult.success();
        }
        return AjaxResult.error("修改个人信息异常,请联系管理员");
    }

    /**
     * 重置密码
     */
    @Log(title = "个人信息", businessType = BusinessType.UPDATE)
    @PutMapping("/updatePwd")
    public AjaxResult updatePwd(String oldPassword, String newPassword)
    {
        LoginUser loginUser = getLoginUser();
        String userName = loginUser.getUsername();
        String password = loginUser.getPassword();
        if (!SecurityUtils.matchesPassword(oldPassword, password))
        {
            return AjaxResult.error("修改密码失败,旧密码错误");
        }
        if (SecurityUtils.matchesPassword(newPassword, password))
        {
            return AjaxResult.error("新密码不能与旧密码相同");
        }
        if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0)
        {
            // 更新缓存用户密码
            loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword));
            tokenService.setLoginUser(loginUser);
            return AjaxResult.success();
        }
        return AjaxResult.error("修改密码异常,请联系管理员");
    }

    /**
     * 头像上传
     */
    @Log(title = "用户头像", businessType = BusinessType.UPDATE)
    @PostMapping("/avatar")
    public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws IOException
    {
        if (!file.isEmpty())
        {
            LoginUser loginUser = getLoginUser();
            String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file);
            if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
            {
                AjaxResult ajax = AjaxResult.success();
                ajax.put("imgUrl", avatar);
                // 更新缓存用户头像
                loginUser.getUser().setAvatar(avatar);
                tokenService.setLoginUser(loginUser);
                return ajax;
            }
        }
        return AjaxResult.error("上传图片异常,请联系管理员");
    }
}

这是一个通用的基础控制器类,用于处理 Web 层的通用数据操作。以下是一些观察和建议:

  1. 日志记录:

    • 使用了SLF4J进行日志记录,这是个好实践。
  2. 日期转换:

    • 通过initBinder方法实现了将前端传递的日期字符串自动转换为Date类型,这是一个很好的功能。
  3. 分页处理:

    • 使用了PageHelper库来处理分页,这是典型的MyBatis分页处理方式。确保TableSupportPageDomain类的实现是正确的。
  4. 结果封装:

    • AjaxResult类用于封装响应数据,这有助于在控制器中一致地处理返回结果。
  5. 安全性:

    • 使用SecurityUtils来获取登录用户的信息,这是常见的安全实践。
  6. 异常处理:

    • 缺少全局异常处理,可以考虑添加一个全局异常处理器,以便在发生异常时提供友好的错误信息。
  7. 业务逻辑方法:

    • 一些通用的业务逻辑方法,如获取登录用户信息、跳转页面等,有助于避免在每个控制器中都重复编写相似的代码。
  8. 重定向方法:

    • redirect方法用于生成重定向路径,这是一个很好的抽象。

2.2 BaseController

源码:

package com.ruoyi.common.core.controller;

import java.beans.PropertyEditorSupport;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.sql.SqlUtil;

/**
 * web层通用数据处理
 * 
 * @author ruoyi
 */
public class BaseController
{
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 将前台传递过来的日期格式的字符串,自动转化为Date类型
     */
    @InitBinder
    public void initBinder(WebDataBinder binder)
    {
        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
        {
            @Override
            public void setAsText(String text)
            {
                setValue(DateUtils.parseDate(text));
            }
        });
    }

    /**
     * 设置请求分页数据
     */
    protected void startPage()
    {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
        {
            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
            Boolean reasonable = pageDomain.getReasonable();
            PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
        }
    }

    /**
     * 设置请求排序数据
     */
    protected void startOrderBy()
    {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        if (StringUtils.isNotEmpty(pageDomain.getOrderBy()))
        {
            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
            PageHelper.orderBy(orderBy);
        }
    }

    /**
     * 响应请求分页数据
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected TableDataInfo getDataTable(List<?> list)
    {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(list);
        rspData.setTotal(new PageInfo(list).getTotal());
        return rspData;
    }

    /**
     * 返回成功
     */
    public AjaxResult success()
    {
        return AjaxResult.success();
    }

    /**
     * 返回失败消息
     */
    public AjaxResult error()
    {
        return AjaxResult.error();
    }

    /**
     * 返回成功消息
     */
    public AjaxResult success(String message)
    {
        return AjaxResult.success(message);
    }

    /**
     * 返回失败消息
     */
    public AjaxResult error(String message)
    {
        return AjaxResult.error(message);
    }

    /**
     * 响应返回结果
     * 
     * @param rows 影响行数
     * @return 操作结果
     */
    protected AjaxResult toAjax(int rows)
    {
        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
    }

    /**
     * 响应返回结果
     * 
     * @param result 结果
     * @return 操作结果
     */
    protected AjaxResult toAjax(boolean result)
    {
        return result ? success() : error();
    }

    /**
     * 页面跳转
     */
    public String redirect(String url)
    {
        return StringUtils.format("redirect:{}", url);
    }

    /**
     * 获取用户缓存信息
     */
    public LoginUser getLoginUser()
    {
        return SecurityUtils.getLoginUser();
    }

    /**
     * 获取登录用户id
     */
    public Long getUserId()
    {
        return getLoginUser().getUserId();
    }

    /**
     * 获取登录部门id
     */
    public Long getDeptId()
    {
        return getLoginUser().getDeptId();
    }

    /**
     * 获取登录用户名
     */
    public String getUsername()
    {
        return getLoginUser().getUsername();
    }
}

以下是对代码的一些建议:

  1. 代码结构:

    • 代码结构良好,符合Spring MVC控制器的典型结构。
  2. 注解使用:

    • 使用@RestController@RequestMapping@Autowired等注解是Spring Boot控制器的典型使用方式。
  3. 错误处理:

    • 使用AjaxResult类返回错误消息。建议为常见的错误场景创建一个实用方法或全局异常处理程序,以保持一致的错误处理。
  4. 密码处理:

    • 使用SecurityUtils.matchesPassword对密码进行哈希和检查,这增强了安全性。
  5. 文件上传:

    • 文件上传处理看起来是合适的。在处理之前检查文件是否不为空。
  6. 安全性:

    • 确保方法具有足够的安全性。可能根据应用程序的安全性要求添加适当的安全性注解(@PreAuthorize等)。
  7. 注释:

    • 考虑添加注释以解释复杂的部分或业务逻辑。
  8. 一致性:

    • 确保在命名约定上保持一致。例如,userNameuser.getNickName()的使用可能要保持一致。
  9. 异常处理:

    • 对上传头像时的IOException进行了通用捕获。对于调试目的,记录异常详细信息可能会有帮助。
  10. RESTful约定:

    • 确保遵循RESTful约定。例如,如果适用,考虑使用@PatchMapping而不是@PutMapping进行部分更新。
  11. 硬编码字符串:

    • 谨慎使用硬编码的字符串,如"/system/user/profile/avatar"。最好将这些字符串定义为常量。
  12. 个人资料图像更新:

    • 最好仅在数据库操作成功时更新缓存。目前,即使头像更新失败,缓存也会被更新。

2.3 CaptchaController

源码:

package com.ruoyi.web.controller.common;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.google.code.kaptcha.Producer;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.sign.Base64;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.system.service.ISysConfigService;

/**
 * 验证码操作处理
 * 
 * @author ruoyi
 */
@RestController
public class CaptchaController
{
    @Resource(name = "captchaProducer")
    private Producer captchaProducer;

    @Resource(name = "captchaProducerMath")
    private Producer captchaProducerMath;

    @Autowired
    private RedisCache redisCache;
    
    // 验证码类型
    @Value("${ruoyi.captchaType}")
    private String captchaType;
    
    @Autowired
    private ISysConfigService configService;
    /**
     * 生成验证码
     */
    @GetMapping("/captchaImage")
    public AjaxResult getCode(HttpServletResponse response) throws IOException
    {
        AjaxResult ajax = AjaxResult.success();
        boolean captchaOnOff = configService.selectCaptchaOnOff();
        ajax.put("captchaOnOff", captchaOnOff);
//        if (!captchaOnOff)
//        {
//            return ajax;
//        }
        if (!captchaOnOff)
        {
            return ajax;
        }


        // 保存验证码信息
        String uuid = IdUtils.simpleUUID();
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;

        String capStr = null, code = null;
        BufferedImage image = null;

        // 生成验证码
        if ("math".equals(captchaType))
        {
            String capText = captchaProducerMath.createText();
            capStr = capText.substring(0, capText.lastIndexOf("@"));
            code = capText.substring(capText.lastIndexOf("@") + 1);
            image = captchaProducerMath.createImage(capStr);
        }
        else if ("char".equals(captchaType))
        {
            capStr = code = captchaProducer.createText();
            image = captchaProducer.createImage(capStr);
        }

        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
        // 转换流信息写出
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        try
        {
            ImageIO.write(image, "jpg", os);
        }
        catch (IOException e)
        {
            return AjaxResult.error(e.getMessage());
        }

        ajax.put("uuid", uuid);
        ajax.put("img", Base64.encode(os.toByteArray()));
        return ajax;
    }
}

这是一个处理验证码操作的控制器类,整体上看起来符合一般的 Java 和 Spring Boot 的代码规范。以下是一些建议:

  1. 注释:

    • 添加一些注释,特别是对于非常规或者复杂的逻辑,这将有助于其他开发者更容易理解你的代码。
  2. 日志记录:

    • 在代码中缺少日志记录。在关键的地方(如异常捕获、业务逻辑)添加日志将有助于调试和跟踪问题。
  3. 注入方式:

    • 注入使用了 @Resource@Autowired 两种方式,这两者可以混合使用,但通常会使用一种保持一致性。在整个类中可以选择使用一种方式。
  4. Magic Values:

    • 在代码中有一些 “math” 和 “char” 的字符串,最好将其定义为常量,这样可以提高代码的可维护性。
  5. 异常处理:

    • 在生成验证码的过程中,可能会抛出 IOException 异常,建议在方法签名上声明可能抛出的异常,并在发生异常时返回适当的错误信息。
  6. 代码逻辑:

    • 在生成验证码时,通过 Base64.encode(os.toByteArray()) 将验证码转换为 Base64 编码,这是合理的做法,但确保客户端能够正确地处理 Base64 图片。
  7. 配置项:

    • captchaType 从配置文件中获取,确保这个配置项在配置文件中有明确的说明。
  8. 一致性:

    • 在整个项目中保持一致性非常重要。确保在命名、风格和用法上保持一致,这有助于提高代码的可读性和维护性。

总体来说,这个控制器类看起来清晰且符合一般的最佳实践。确保在您的团队或项目中遵循一致的规范。

3. 修改意见

3.1 SysProfileController

  1. 异常处理:
    • 缺少全局异常处理,可以考虑添加一个全局异常处理器,以便在发生异常时提供友好的错误信息。

3.2 BaseController

  1. 错误处理:

    • 使用AjaxResult类返回错误消息。建议为常见的错误场景创建一个实用方法或全局异常处理程序,以保持一致的错误处理。
  2. 注释:

    • 考虑添加注释以解释复杂的部分或业务逻辑。
  3. 一致性:

    • 确保在命名约定上保持一致。例如,userNameuser.getNickName()的使用可能要保持一致。
  4. 异常处理:

    • 对上传头像时的IOException进行了通用捕获。对于调试目的,记录异常详细信息可能会有帮助。
  5. RESTful约定:

    • 确保遵循RESTful约定。例如,如果适用,考虑使用@PatchMapping而不是@PutMapping进行部分更新。
  6. 硬编码字符串:

    • 谨慎使用硬编码的字符串,如"/system/user/profile/avatar"。最好将这些字符串定义为常量。
  7. 个人资料图像更新:

    • 最好仅在数据库操作成功时更新缓存。目前,即使头像更新失败,缓存也会被更新。

3.3 CaptchaController

  1. 注释:

    • 添加一些注释,特别是对于非常规或者复杂的逻辑,这将有助于其他开发者更容易理解你的代码。
  2. 日志记录:

    • 在代码中缺少日志记录。在关键的地方(如异常捕获、业务逻辑)添加日志将有助于调试和跟踪问题。
  3. 注入方式:

    • 注入使用了 @Resource@Autowired 两种方式,这两者可以混合使用,但通常会使用一种保持一致性。在整个类中可以选择使用一种方式。
  4. 异常处理:

    • 在生成验证码的过程中,可能会抛出 IOException 异常,建议在方法签名上声明可能抛出的异常,并在发生异常时返回适当的错误信息。
  5. 代码逻辑:

    • 在生成验证码时,通过 Base64.encode(os.toByteArray()) 将验证码转换为 Base64 编码,这是合理的做法,但确保客户端能够正确地处理 Base64 图片。

4. 总结

总体来说,代码质量良好,但建议根据上述测试点和修改意见进行相应的改进,以提高系统的稳定性、性能和安全性。

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值