基于SpringBoot从零构建博客网站 - 设计可扩展上传模块和开发修改头像密码功能

上传模块在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;
}

关注我

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


架构与我
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,517评论 6 13
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,347评论 0 15
  • 朋友圈看到去年此时和老同学的聚会照片,发现对比差别很大,她近一年来坚持运动和锻炼,把她去年说的毕业之后一年涨一斤...
    AK47_10年坚持阅读 207评论 0 0
  • (一) 圣诞节一周前的傍晚,在A城靠近城市中轴线的一个临街体彩店里,人声喧嚷,五六个彩民凑在一起,以一个拿手机播放...
    普洛斯阅读 305评论 0 0