上傳模組在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引數

頁面效果如下:

注意在回顯圖片時,需要加上如下配置:

/**
 * 靜態資源配置
 *
 * @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、修改密碼

修改密碼功能相對簡單,頁面效果如下:

此處就只列出修改密碼的核心邏輯,即:

/**
 * 修改密碼
 *
 * @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;
}

關注我

以你最方便的方式關注我:
微信公眾號: