美文网首页
基于SpringBoot从零构建博客网站 - 设计可扩展上传模块

基于SpringBoot从零构建博客网站 - 设计可扩展上传模块

作者: 架构与我 | 来源:发表于2019-07-15 13:19 被阅读0次

    上传模块在web开发中是很常见的功能也是很重要的功能,在web应用中需要上传的可以是图片、pdf、压缩包等其它类型的文件,同时对于图片可能需要回显,对于其它文件要能够支持下载等。在守望博客系统中对于上传模块进行统一管理,同时对于上传不同的类型文件,留有自定义实现机制的接口,也即可扩展。

    基于上传模块机制,就可以实现修改头像功能了。同时顺带将修改密码的功能也一起实现,这个修改密码的功能相对就很简单了。

    1、可扩展上传模块

    统一上传模块的体现就是上传所有类型的文件,都是调用统一的一个接口,即上传接口唯一;同时对于具体上传的类型文件,如有特殊处理的可以自定义实现处理方法。

    对于上传的文件能够有自定义的实现机制,则需要一个上传文件的处理接口,即:IUploadHandler,内容如下:

    /**
     * 上传文件处理接口类
     *
     * @author lzj
     * @since 1.0
     * @date [2019-07-09]
     */
    public interface IUploadHandler {
    
        /**
         * 上传文件处理方法
         * 文件上传成功,返回文件的相关信息
         * 文件上传失败, 返回null
         *
         * @param file
         * @param distType
         * @param userId
         * @return
         * @throws Exception
         */
        public Object upload(MultipartFile file, String distType, String userId) throws Exception;
    
        /**
         * 下载文件
         *
         * @param fileId
         * @param response
         * @throws Exception
         */
        public void download(String fileId, HttpServletResponse response) throws Exception;
    
        /**
         * 根据条件列出文件信息
         *
         * @param distType
         * @param userId
         * @return
         * @throws Exception
         */
        public Object list(String distType, String userId) throws Exception;
    }
    

    目前本版本中暂定有3个方法,即:

    • upload方法,用于处理自定义上传文件方式;
    • download方法,用于处理自定义下载的方式;
    • list方法,用于处理自定义列出文件列表的方式。

    这里以上传头像图片为例,则上传头像的实现类UploadAvatarHandler,内容如下:

    /**
     * 上传头像处理类
     *
     * @author lzj
     * @since 1.0
     * @date [2019-07-09]
     */
    @Slf4j
    @Component("_avatar")
    public class UploadAvatarHandler implements IUploadHandler {
    
        @Autowired
        private IUserService userService;
    
        @Resource(name = "configCache")
        private ICache<Config> configCache;
    
        @Override
        public Object upload(MultipartFile file, String distType, String userId) throws Exception {
            Map<String, Object> result = new HashMap<String, Object>();
            try {
                // 获取图片的大小
                long fileSize = file.getSize();
    
                // 图片大小不能超过2M, 2M = 2 * 1024 * 1024B = 2097152B
                if (fileSize > 2097152L) {
                    throw new TipException("您上传的图片超过2M");
                }
    
                Config config = configCache.get(Config.CONFIG_IMG_AVATAR_PATH);
                // 保存头像的根目录
                String basePath = config.getConfigValue();
                if (!basePath.endsWith("/")) {
                    basePath += "/";
                }
    
                // 根据当前时间构建yyyyMM的文件夹,建立到月的文件夹
                String dateDirName = DateUtil.date2Str(new Date(), DateUtil.YEAR_MONTH_FORMAT);
                basePath += dateDirName;
    
                File imageDir = new File(basePath);
                if (!imageDir.exists()) {
                    imageDir.mkdirs();
                }
    
                String fileNewName = IdGenarator.guid() + ".jpg";
                FileUtil.copy(file.getInputStream(), new FileOutputStream(new File(imageDir, fileNewName)));
    
                // 获取用户信息
                User user = userService.getById(userId);
                user.setPicture(dateDirName + "/" + fileNewName);
    
                // 更新信息
                userService.updateById(user);
    
                result.put("success", true);
                result.put("msg", "上传头像成功");
            } catch (TipException e) {
                result.put("success", false);
                result.put("msg", e.getMessage());
            } catch (Exception e) {
                log.error("上传头像失败", e);
                result.put("success", false);
                result.put("msg", "上传头像失败");
            }
    
            return result;
        }
    
        @Override
        public void download(String fileId, HttpServletResponse response) throws Exception {
        }
    
        @Override
        public Object list(String distType, String userId) throws Exception {
            return null;
        }
    

    这里有2个注意点,即这个@Component("_avatar"),这个类的名称最好自定义命名,最好以处理这种文件的类型为名,例如此处的是处理头像的,所以就是avatar,但是为了防止重名,所以前缀加上了下划线。

    另外一个需要注意的就是,并不是所有的方法都需要实现,例如此处就没有实现download和list方法,因为头像图片不是通过流的方式回显的,而是直接通过映射到具体的图片,同时也是不需要列出头像的功能。

    前面说过所有上传文件,都是调用统一的一个接口,也即是UploadController,内容如下:

    /**
     * 处理文件上传下载控制器类
     *
     * @author lzj
     * @date [2019-07-09]
     * @since 1.0
     */
    @Slf4j
    @Controller
    public class UploadController {
    
        @Autowired
        private ApplicationContext context;
    
        // 用于存储处理上传文件对象
        private Map<String, IUploadHandler> uploadHandlers;
    
        /**
         * 初始化操作
         *
         * @throws Exception
         */
        @PostConstruct
        public void init() throws Exception {
            uploadHandlers = context.getBeansOfType(IUploadHandler.class);
        }
    
        /**
         * 上传文件
         *
         * @param file
         * @param request
         * @param session
         * @return
         */
        @RequestMapping(value = "/upload", method = RequestMethod.POST)
        @ResponseBody
        public Object upload(@RequestParam(value = "_uploadFile", required = false) MultipartFile file, HttpServletRequest request, HttpSession session) {
            Object result = null;
            try {
                // 接收参数
                // 获取上传文件类型,参数名为_fileType
                String _distType = request.getParameter("_distType");
    
                // 获取用户信息
                User user = (User) session.getAttribute(Const.SESSION_USER);
    
                result = uploadHandlers.get(_distType).upload(file, _distType, user.getUserId());
            } catch (Exception e) {
                log.error("上传文件失败", e);
            }
            return result;
        }
    }
    

    这里需要注意init方法,该方法会将IUploadHandler接口的实现类都扫描出来,同时以类名为key,实例为value返回。

    同时调用上传方法时是需要带_distType参数的,该参数值要与具体IUploadHandler的实现类的类名一样,例如:上传头像就需要将 _distType = _avatar参数带过来。这样UploadController就知道具体用哪个实现类来处理。

    2、修改头像

    有了前面的上传模块,对于修改头像就简单多了,首先需要实现上传头像的实现类,即UploadAvatarHandler类,代码在上方已经罗列了此处省略。

    加载出修改头像页面的核心的如下:

    /**
     * 加载出修改头像页面
     *
     * @return
     */
    @RequestMapping(value = "/user/avatar", method = RequestMethod.GET)
    public String avatar(HttpSession session, Model model) {
        // session中的信息
        User sessionUser = (User) session.getAttribute(Const.SESSION_USER);
    
        // 从数据库中获取用户信息
        User user = userService.getById(sessionUser.getUserId());
    
        model.addAttribute("user", user);
        return Const.BASE_INDEX_PAGE + "auth/user/avatar";
    }
    

    修改头像,运用了fullAvatarEditor插件,所以核心的前台代码如下:

    <script type="text/javascript">
        swfobject.addDomLoadEvent(function () {
            var swf = new fullAvatarEditor("${rc.contextPath}/static/plugins/fullAvatarEditor/fullAvatarEditor.swf", "${rc.contextPath}/resources/plugins/fullAvatarEditor/expressInstall.swf", "swfContainer", {
                    id: 'swf',
                    upload_url: '${rc.contextPath}/upload?_distType=_avatar',   //上传接口
                    method: 'post', //传递到上传接口中的查询参数的提交方式。更改该值时,请注意更改上传接口中的查询参数的接收方式
                    src_upload: 0,      //是否上传原图片的选项,有以下值:0-不上传;1-上传;2-显示复选框由用户选择
                    avatar_box_border_width: 0,
                    avatar_sizes: '150*150',
                    avatar_sizes_desc: '150*150像素',
                    avatar_field_names: '_uploadFile'
                }, function (msg) {
                    console.log(msg);
                    switch (msg.code) {
                        case 1 :
                            break;
                        case 2 :
                            document.getElementById("upload").style.display = "inline";
                            break;
                        case 3 :
                            if (msg.type == 0) {
    
                            }
                            else if (msg.type == 1) {
                                alert("摄像头已准备就绪但用户未允许使用!");
                            }
                            else {
                                alert("摄像头被占用!");
                            }
                            break;
                        case 5 :
                            setTimeout(function () {
                                window.location.href = window.location.href;
                            }, 1000);
                            break;
                    }
                }
            );
            document.getElementById("upload").onclick = function () {
                swf.call("upload");
            };
        });
    </script>
    

    注意:

    里面一个upload_url参数就是写上传接口的,上述中为:

    upload_url: '${rc.contextPath}/upload?_distType=_avatar'
    

    正如在前面讨论的一样的,需要带上 _distType参数

    页面效果如下:


    001.jpg

    注意在回显图片时,需要加上如下配置:

    /**
     * 静态资源配置
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 映射头像图片
        String avatarPath = configCache.get(Config.CONFIG_IMG_AVATAR_PATH).getConfigValue();
        if (!avatarPath.endsWith("/")) {
            avatarPath += "/";
        }
        registry.addResourceHandler("/img/avatar/**").addResourceLocations("file:" + avatarPath);
    }
    

    3、修改密码

    修改密码功能相对简单,页面效果如下:


    002.png

    此处就只列出修改密码的核心逻辑,即:

    /**
     * 修改密码
     *
     * @param request
     * @param session
     * @param model
     * @return
     */
    @RequestMapping(value = "/user/password", method = RequestMethod.POST)
    @ResponseBody
    public Result password(HttpServletRequest request, HttpSession session) {
        Result result = new Result();
        try {
            // 获取登录信息
            User tempUser = (User) session.getAttribute(Const.SESSION_USER);
            String userId = tempUser.getUserId();
    
            // 接收参数
            String password = request.getParameter("password");
            String newPwd = request.getParameter("newPwd");
            String confirmNewPwd = request.getParameter("confirmNewPwd");
    
            if (StringUtils.isEmpty(password) || StringUtils.isEmpty(newPwd) || StringUtils.isEmpty(confirmNewPwd)) {
                throw new TipException("缺少必要请求参数");
            }
    
            if (!newPwd.equals(confirmNewPwd)) {
                throw new TipException("两次输入的新密码不相等");
            }
    
            // 获取用户信息
            User user = userService.getById(userId);
            if (!user.getPassword().equals(StringUtil.md5(password))) {
                throw new TipException("旧密码输入不正确");
            }
    
            // 修改密码
            user.setPassword(StringUtil.md5(newPwd));
            boolean flag = userService.updateById(user);
    
            if (!flag) {
                throw new TipException("修改密码失败");
            }
    
            result.setCode(Result.CODE_SUCCESS);
            result.setMsg("修改成功");
        } catch (TipException e) {
            result.setCode(Result.CODE_EXCEPTION);
            result.setMsg(e.getMessage());
        } catch (Exception e) {
            log.error("修改密码失败", e);
            result.setCode(Result.CODE_EXCEPTION);
            result.setMsg("修改密码失败");
        }
        return result;
    }
    

    关注我

    以你最方便的方式关注我:
    微信公众号:


    架构与我

    相关文章

      网友评论

          本文标题:基于SpringBoot从零构建博客网站 - 设计可扩展上传模块

          本文链接:https://www.haomeiwen.com/subject/kscnkctx.html