• [交流吐槽] 关于Java数组的那些事
    数组是相同数据类型的元素的有序集合。为什么说是有序的呢?访问数组元素是通过数组下标进行的,下标从0开始,依次递增直到 length - 1(length为数组长度),所以可以理解为有序的。首先,来谈谈如何初始化数组?两种初始化方法:动态初始化、静态初始化。(这里以一维数组为例)//动态初始化int[] arr1 = new int[9];//声明了一个数组长度为9的一维数组//除了上面的初始化方式,还可以拆分为两步int[] arr2;arr2 = new int [9];//这两种都属于动态初始化,只是方式存在差异//静态初始化,声明数组长度为5的一维数组int[] arr3 = new int[]{1,2,3,4,5};//静态初始化的标准格式//也可以分为两步初始化//int[] arr3;//arr3 = new int[]{1,2,3,4,5};int[] arr4 = {1,2,3,4},5;//静态初始化的省略格式,不能分为两步既然声明了一个数组,那么数组是储存在哪里的呢?当使用"new"关键字创建数组对象后,会在堆中为数组分配相应的内存空间。总的来说,就是当你使用"new"关键字创建一个对象时,就会在堆中为该对象分配相应的内存空间。数组初始化,那么如何访问数组元素进行赋值和获取元素值呢?int[] arr = new int[5];//数组元素的值默认为0//数组元素赋值for(int i = 0; i < 5; i++){arr = 1;//这里就将数组下标为i的元素赋值为1}//既然对数组赋值了,那么如何获取数组元素的值System.out.println(arr);//这样是否会输出数组元素的值呢//答案是不会,这样输出的是数组的内存地址哈希值[I@#########]//方括号代表是数组,大写字母I代表为整型,后面的#则是十六进制数//这里介绍一种循环:for each循环for(int x:arr){System.out.println(x);//这样就能输出数组元素的值了,当然你也可以使用普通的for循环来进行访问//for(int i = 0; i < 5; i++){//System.out.println(arr);//}}以上就是Java数组中关于一维数组的简单介绍。
  • [优秀实践] KML_VML的JAVA语言适配项目实践分享
    KML_VML的JAVA语言适配项目实践分享  很高兴能够为大家提供我们在项目实践过程中所获得的经验,我们团队所做的项目是基于KML_VML的JAVA语言适配项目,我们项目的主要任务就是通过JAVA适配C语言接口,实现跨语言调用的问题。接下来对我们项目的开发过程进行一些分享。项目介绍:此项目是基于鲲鹏平台的openJDK,通过JNI编程框架,实现JAVA程序对C接口动态库的调用,提供KML_VML的JAVA语言适配,完善鲲鹏高性能向量数学库的生态。主要的任务便是做出JAVA与C语言的适配器,同时达标项目的要求。项目要求:实现KML_VML32个接口的JAVA适配,通过JAVA接口单元测试。性能损耗相比C接口调用不超过5%。符合华为编码规范。工作过程:如下图所示,我们的主要工作过程,首先在项目起始阶段,开展项目启动会,会后进行详细的需求分析,并根据内容做出详尽的设计方案,然后,依照需求分析,设计方案,项目的要求等等从而做出详尽的工作计划。工作计划:按照迭代的方式进行开发,主要分为了三个阶段性任务,包括技术方案设计,开发撰写报告(阶段性交付),验收和总结。计划是根据项目的规划,以及工作量进行详细的设计,尽量按照时间稳步推进。如图所示,展示了部分计划的安排方式。同时,为了项目稳步推进,就需要与老师,与华为方定期进行会议讨论,汇报进度,以下是会议的安排方式,以及图示的会议纪要1、每周实验室内部开展组会,面对面形式进行汇报,包括进度的汇报,项目开发内容,遇到的问题讨论等。2、每两周与华为方进行线上会议的汇报,稳定推进项目,其中内容,包括了项目进度,开发内容,共同协商遇到的问题,并记录会议纪要,撰写周报,对该阶段的项目开发进行总结。3、除此之外,遇到问题,需要多多通过聊天等方式进行联系,尽快确定问题解决方式。遇到的问题及解决方式因为部分函数本身特性,跨语言调用传参占用时间显得过于大,造成效率低解决:我们先制订比较合适的测试方案,再与华为方进行协商讨论1、由于JAVA的语言特性,需要制定更加详细,更加符合的测试方案2、根据实际情况选定合适的数量级参数进行测试3、选取不同数量级的参数,统计变异系数,从而选定最稳定的测试情况同时根据不同的参数与函数指定可展现编译系数的图表,以找寻稳定的测试情况,如下图所示,该图表示程序运行时的变异参数,其中横轴为参数指标,纵轴为变异系数(变异系数=标准差/均值,该指标可反映数据的稳定性)通过分析上述的原因,进行迭代开发,优化性能测试,从而得出:1、所有性能指标均达标2、提供多种测试方式,包括全量测试,单例测试3、动态生成测试用例,实时展示测试后的效率,并以表格形式进行汇总4、多种测试案例,汇总到表格中,提供更详细的性能表格 同时在项目开发过程中,必不可少的会遇到一些细节方面的问题1、服务器环境问题2、功能指标问题3、性能指标问题4、兼容性问题5、项目及文档交付问题解决方式:1、多开会交流,比较大的问题在网络会议中讨论解决方式,小问题,可以直接在群聊中交流,更快得到双方的讨论结果2、根据既定的目标,不断迭代开发,保证新版本比之前的版本,功能性能,以及代码质量都得到提升3、可以多采用软件工程的开发方式,如敏捷开发,迭代开发等等项目的总结与感受: 1、在项目前期进行完善的需求分析,采用软件工程的方式对项目进行管理,制定合适的项目计划2、撰写设计方案,综合考量项目的需求,目标,制定合理的设计方案3、对项目开发的文档,代码进行统一管理,使用一定的开发模式4、多开会沟通,定期的会议是尤为重要的,及时反映开发的问题才能做到事半功倍5、华为方的设计与规划也都十分的合适,同时与华为方的沟通也非常顺畅,能够及时沟通解决遇到的问题,项目管控好,隔周开会可以逐步稳定推进项目进行,同时这也让我们团队学习到了许多项目管理知识,期待以后能够与华为有更多的合作。                                                                      北京邮电大学-智能应用软件开发团队-郭成旺                                                                             指导教师:杨正球,修佳鹏
  • [技术干货] Java项目:在线嘿嘿网盘系统设计和实现(java+Springboot+ssm+mysql+maven)[转载下]
        @GetMapping("/downloadFile")    public String downloadFile(@RequestParam Integer fId){        if (fileStoreService.getFileStoreByUserId(loginUser.getUserId()).getPermission() == 2){            logger.error("用户没有下载文件的权限!下载失败...");            return "redirect:/error401Page";        }        //获取文件信息        MyFile myFile = myFileService.getFileByFileId(fId);        String remotePath = myFile.getMyFilePath();        String fileName = myFile.getMyFileName()+myFile.getPostfix();        try {            //去FTP上拉取            OutputStream os = new BufferedOutputStream(response.getOutputStream());            response.setCharacterEncoding("utf-8");            // 设置返回类型            response.setContentType("multipart/form-data");            // 文件名转码一下,不然会出现中文乱码            response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, "UTF-8"));            boolean flag = FtpUtil.downloadFile("/" + remotePath, fileName, os);            if (flag) {                myFileService.updateFile(                        MyFile.builder().myFileId(myFile.getMyFileId()).downloadTime(myFile.getDownloadTime() + 1).build());                os.flush();                os.close();                logger.info("文件下载成功!" + myFile);            }        } catch (Exception e) {            e.printStackTrace();        }        return "success";    }     /**     * @Description 删除文件     * @Author xw     * @Date 23:14 2020/2/10     * @Param [fId, folder]     * @return java.lang.String     **/    @GetMapping("/deleteFile")    public String deleteFile(@RequestParam Integer fId,Integer folder){        //获得文件信息        MyFile myFile = myFileService.getFileByFileId(fId);        String remotePath = myFile.getMyFilePath();        String fileName = myFile.getMyFileName()+myFile.getPostfix();        //从FTP文件服务器上删除文件        boolean b = FtpUtil.deleteFile("/"+remotePath, fileName);        if (b){            //删除成功,返回空间            fileStoreService.subSize(myFile.getFileStoreId(),Integer.valueOf(myFile.getSize()));            //删除文件表对应的数据            myFileService.deleteByFileId(fId);        }        logger.info("删除文件成功!"+myFile);        return "redirect:/files?fId="+folder;    }        /**     * @Description 删除文件夹并清空文件     * @Author xw     * @Date 15:22 2020/2/12     * @Param [fId]     * @return java.lang.String     **/    @GetMapping("/deleteFolder")    public String deleteFolder(@RequestParam Integer fId){        FileFolder folder = fileFolderService.getFileFolderByFileFolderId(fId);        //强制删除        deleteFolderF(folder);        return folder.getParentFolderId() == 0?"redirect:/files":"redirect:/files?fId="+folder.getParentFolderId();    }      /**     * @Description 迭代删除文件夹里面的所有文件和子文件夹     * @Author xw     * @Date 9:17 2020/2/29     * @Param [folder]     * @return void     **/    public void deleteFolderF(FileFolder folder){        //获得当前文件夹下的所有子文件夹        List<FileFolder> folders = fileFolderService.getFileFolderByParentFolderId(folder.getFileFolderId());        //删除当前文件夹的所有的文件        List<MyFile> files = myFileService.getFilesByParentFolderId(folder.getFileFolderId());        if (files.size()!=0){            for (int i = 0; i < files.size(); i++) {                Integer fileId = files.get(i).getMyFileId();                boolean b = FtpUtil.deleteFile("/"+files.get(i).getMyFilePath(), files.get(i).getMyFileName() + files.get(i).getPostfix());                if (b){                    myFileService.deleteByFileId(fileId);                    fileStoreService.subSize(folder.getFileStoreId(),Integer.valueOf(files.get(i).getSize()));                }            }        }        if (folders.size()!=0){            for (int i = 0; i < folders.size(); i++) {                deleteFolderF(folders.get(i));            }        }        fileFolderService.deleteFileFolderById(folder.getFileFolderId());    }     /**     * @Description 添加文件夹     * @Author xw     * @Date 23:16 2020/2/10     * @Param [folder, map]     * @return java.lang.String     **/    @PostMapping("/addFolder")    public String addFolder(FileFolder folder,Map<String, Object> map) {        //设置文件夹信息        folder.setFileStoreId(loginUser.getFileStoreId());        folder.setTime(new Date());        //获得当前目录下的所有文件夹,检查当前文件夹是否已经存在        List<FileFolder> fileFolders = null;        if (folder.getParentFolderId() == 0){            //向用户根目录添加文件夹            fileFolders = fileFolderService.getRootFoldersByFileStoreId(loginUser.getFileStoreId());        }else{            //向用户的其他目录添加文件夹            fileFolders = fileFolderService.getFileFolderByParentFolderId(folder.getParentFolderId());        }        for (int i = 0; i < fileFolders.size(); i++) {            FileFolder fileFolder = fileFolders.get(i);            if (fileFolder.getFileFolderName().equals(folder.getFileFolderName())){                logger.info("添加文件夹失败!文件夹已存在...");                return "redirect:/files?error=1&fId="+folder.getParentFolderId();            }        }        //向数据库写入数据        Integer integer = fileFolderService.addFileFolder(folder);        logger.info("添加文件夹成功!"+folder);        return "redirect:/files?fId="+folder.getParentFolderId();    }        /**     * @Description 重命名文件夹     * @Author xw     * @Date 23:18 2020/2/10     * @Param [folder, map]     * @return java.lang.String     **/    @PostMapping("/updateFolder")    public String updateFolder(FileFolder folder,Map<String, Object> map) {        //获得文件夹的数据库信息        FileFolder fileFolder = fileFolderService.getFileFolderByFileFolderId(folder.getFileFolderId());        fileFolder.setFileFolderName(folder.getFileFolderName());        //获得当前目录下的所有文件夹,用于检查文件夹是否已经存在        List<FileFolder> fileFolders = fileFolderService.getFileFolderByParentFolderId(fileFolder.getParentFolderId());        for (int i = 0; i < fileFolders.size(); i++) {            FileFolder folder1 = fileFolders.get(i);            if (folder1.getFileFolderName().equals(folder.getFileFolderName()) && folder1.getFileFolderId() != folder.getFileFolderId()){                logger.info("重命名文件夹失败!文件夹已存在...");                return "redirect:/files?error=2&fId="+fileFolder.getParentFolderId();            }        }        //向数据库写入数据        Integer integer = fileFolderService.updateFileFolderById(fileFolder);        logger.info("重命名文件夹成功!"+folder);        return "redirect:/files?fId="+fileFolder.getParentFolderId();    }     /**     * @Description 重命名文件     * @Author xw     * @Date 12:47 2020/2/12     * @Param [file, map]     * @return java.lang.String     **/    @PostMapping("/updateFileName")    public String updateFileName(MyFile file,Map<String, Object> map) {        MyFile myFile = myFileService.getFileByFileId(file.getMyFileId());        if (myFile != null){            String oldName = myFile.getMyFileName();            String newName = file.getMyFileName();            if (!oldName.equals(newName)){                boolean b = FtpUtil.reNameFile(myFile.getMyFilePath() + "/" + oldName+myFile.getPostfix(), myFile.getMyFilePath() + "/" + newName+myFile.getPostfix());                if (b){                    Integer integer = myFileService.updateFile(                            MyFile.builder().myFileId(myFile.getMyFileId()).myFileName(newName).build());                    if (integer == 1){                        logger.info("修改文件名成功!原文件名:"+oldName+"  新文件名:"+newName);                    }else{                        logger.error("修改文件名失败!原文件名:"+oldName+"  新文件名:"+newName);                    }                }            }        }        return "redirect:/files?fId="+myFile.getParentFolderId();    }     /**     * @Description 获得二维码      * @Author xw     * @Date 15:20 2020/2/12     * @Param [id, url]     * @return java.util.Map<java.lang.String,java.lang.Object>     **/    @GetMapping("getQrCode")    @ResponseBody    public Map<String,Object> getQrCode(@RequestParam Integer id,@RequestParam String url){        Map<String,Object> map = new HashMap<>();        map.put("imgPath","https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2654852821,3851565636&fm=26&gp=0.jpg");        if (id != null){            MyFile file = myFileService.getFileByFileId(id);            if (file != null){                try {                    String path = request.getSession().getServletContext().getRealPath("/user_img/");                    url = url+"/file/share?t="+ UUID.randomUUID().toString().substring(0,10) +"&f="+file.getMyFileId()+"&p="+file.getUploadTime().getTime()+""+file.getSize()+"&flag=1";                    File targetFile = new File(path, "");                    if (!targetFile.exists()) {                        targetFile.mkdirs();                    }                    File f = new File(path, id + ".jpg");                    if (!f.exists()){                        //文件不存在,开始生成二维码并保存文件                        OutputStream os = new FileOutputStream(f);                        QRCodeUtil.encode(url, "/static/img/logo.png", os, true);                        os.close();                    }                    map.put("imgPath","user_img/"+id+".jpg");                    map.put("url",url);                } catch (Exception e) {                    e.printStackTrace();                }            }        }        return map;    }        /**     * @Description 分享文件     * @Author xw     * @Date 14:23 2020/2/12     * @Param [fId]     * @return void     **/    @GetMapping("/file/share")    public String shareFile(Integer f,String p,String t,Integer flag){        String fileNameTemp = "";        String remotePath = "";        String fileName = "";        Integer times = 0;        if (flag == null || f == null || p == null || t == null){            logger.info("下载分享文件失败,参数错误");            return "redirect:/error400Page";        }        if(flag == 1){            //获取文件信息            MyFile myFile = myFileService.getFileByFileId(f);            if (myFile == null){                return "redirect:/error404Page";            }            String pwd = myFile.getUploadTime().getTime()+""+myFile.getSize();            if (!pwd.equals(p)){                return "redirect:/error400Page";            }            remotePath = myFile.getMyFilePath();            fileName = myFile.getMyFileName()+myFile.getPostfix();        }else if(flag == 2){            TempFile tempFile = tempFileService.queryById(f);            if (tempFile == null){                return "redirect:/error404Page";            }            Long test = tempFile.getUploadTime().getTime();             String pwd = tempFile.getSize();            if (!pwd.equals(p)){                return "redirect:/error400Page";            }            remotePath = tempFile.getFilePath();            fileName = tempFile.getFileName();        }else {            return "redirect:/error400Page";        }        fileNameTemp = fileName;        try {            //解决下载文件时 中文文件名乱码问题            boolean isMSIE = isMSBrowser(request);            if (isMSIE) {                //IE浏览器的乱码问题解决                fileNameTemp = URLEncoder.encode(fileNameTemp, "UTF-8");            } else {                //万能乱码问题解决                fileNameTemp = new String(fileNameTemp.getBytes("UTF-8"), "ISO-8859-1");            }            //去FTP上拉取            OutputStream os = new BufferedOutputStream(response.getOutputStream());            response.setCharacterEncoding("utf-8");            // 设置返回类型            response.setContentType("multipart/form-data");            // 文件名转码一下,不然会出现中文乱码            response.setHeader("Content-Disposition", "attachment;fileName=" + fileNameTemp);            if (FtpUtil.downloadFile("/" + remotePath, fileName, os)) {                myFileService.updateFile(                        MyFile.builder().myFileId(f).downloadTime(times + 1).build());                os.flush();                os.close();                logger.info("文件下载成功!");            }        } catch (Exception e) {            e.printStackTrace();        }        return "success";    }     /**     * @Description 根据文件的后缀名获得对应的类型     * @Author xw     * @Date 23:20 2020/2/10     * @Param [type]     * @return int 1:文本类型   2:图像类型  3:视频类型  4:音乐类型  5:其他类型     **/    public int getType(String type){        if (".chm".equals(type)||".txt".equals(type)||".xmind".equals(type)||".xlsx".equals(type)||".md".equals(type)                ||".doc".equals(type)||".docx".equals(type)||".pptx".equals(type)                ||".wps".equals(type)||".word".equals(type)||".html".equals(type)||".pdf".equals(type)){            return  1;        }else if (".bmp".equals(type)||".gif".equals(type)||".jpg".equals(type)||".ico".equals(type)||".vsd".equals(type)                ||".pic".equals(type)||".png".equals(type)||".jepg".equals(type)||".jpeg".equals(type)||".webp".equals(type)                ||".svg".equals(type)){            return 2;        } else if (".avi".equals(type)||".mov".equals(type)||".qt".equals(type)                ||".asf".equals(type)||".rm".equals(type)||".navi".equals(type)||".wav".equals(type)                ||".mp4".equals(type)||".mkv".equals(type)||".webm".equals(type)){            return 3;        } else if (".mp3".equals(type)||".wma".equals(type)){            return 4;        } else {            return 5;        }    }        /**     * @Description 正则验证文件名是否合法 [汉字,字符,数字,下划线,英文句号,横线]     * @Author xw     * @Date 23:22 2020/2/10     * @Param [target]     * @return boolean     **/    public boolean checkTarget(String target) {        final String format = "[^\\u4E00-\\u9FA5\\uF900-\\uFA2D\\w-_.]";        Pattern pattern = Pattern.compile(format);        Matcher matcher = pattern.matcher(target);        return !matcher.find();    }     /**     * @Description 判断当前浏览器是否为ie     * @Author xw     * @Date 22:39 2020/3/5     * @Param [request]     * @return boolean     **/    public static boolean isMSBrowser(HttpServletRequest request) {        String[] IEBrowserSignals = {"MSIE", "Trident", "Edge"};        String userAgent = request.getHeader("User-Agent");        for (String signal : IEBrowserSignals) {            if (userAgent.contains(signal)){                return true;            }        }        return false;    }原文链接:https://blog.csdn.net/m0_66863468/article/details/122853352
  • [技术干货] Java项目:在线嘿嘿网盘系统设计和实现(java+Springboot+ssm+mysql+maven)[转载中]
     public String uploadTempFile(@RequestParam("file") MultipartFile file,String url) {        session.setAttribute("imgPath","https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=2654852821,3851565636&fm=26&gp=0.jpg");        String name = file.getOriginalFilename().replaceAll(" ","");        if (!checkTarget(name)){            logger.error("临时文件上传失败!文件名不符合规范...");            session.setAttribute("msg", "上传失败!文件名不符合规范");            return "redirect:/temp-file";        }        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");        String dateStr = format.format(new Date());        String path = "temp/"+dateStr +"/"+UUID.randomUUID();        try {            if (FtpUtil.uploadFile("/"+path, name, file.getInputStream())){                //上传成功                logger.info("临时文件上传成功!"+name);                String size = String.valueOf(file.getSize());                TempFile tempFile = TempFile.builder().fileName(name).filePath(path).size(size).uploadTime(new Date()).build();                if (tempFileService.insert(tempFile)) {                    try {                        String id = UUID.randomUUID().toString();                        String p = request.getSession().getServletContext().getRealPath("/user_img/");                        Long t = tempFile.getUploadTime().getTime();                        url = url+"/file/share?t="+ UUID.randomUUID().toString().substring(0,10) +"&f="+tempFile.getFileId()+"&p="+size+"&flag=2";                        File targetFile = new File(p, "");                        if (!targetFile.exists()) {                            targetFile.mkdirs();                        }                        File f = new File(p, id + ".jpg");                        if (!f.exists()){                            //文件不存在,开始生成二维码并保存文件                            OutputStream os = new FileOutputStream(f);                            QRCodeUtil.encode(url, "/static/img/logo.png", os, true);                            os.close();                        }                        //异步删除临时文件                        tempFileService.deleteById(tempFile.getFileId());                        session.setAttribute("imgPath","user_img/"+id+".jpg");                        session.setAttribute("url",url);                        session.setAttribute("msg","上传成功,扫码/访问链接 即可下载!");                        return "redirect:/temp-file";                    } catch (Exception e) {                        e.printStackTrace();                    }                }else {                    logger.info("临时文件数据库写入失败!"+name);                    session.setAttribute("url","error");                    session.setAttribute("msg", "服务器出错了,临时文件上传失败!");                }            }else{                //上传失败                logger.info("临时文件上传失败!"+name);                session.setAttribute("url","error");                session.setAttribute("msg", "服务器出错了,上传失败!");            }        } catch (IOException e) {            e.printStackTrace();        }        return "redirect:/temp-file";    }     /**     * @Description 网盘的文件上传     * @Author xw     * @Date 23:10 2020/2/10     * @Param [files]     * @return java.util.Map<java.lang.String,java.lang.Object>     **/    @PostMapping("/uploadFile")    @ResponseBody    public Map<String, Object> uploadFile(@RequestParam("file") MultipartFile files) {        Map<String, Object> map = new HashMap<>();        if (fileStoreService.getFileStoreByUserId(loginUser.getUserId()).getPermission() != 0){            logger.error("用户没有上传文件的权限!上传失败...");            map.put("code", 499);            return map;        }        FileStore store = fileStoreService.getFileStoreByUserId(loginUser.getUserId());        Integer folderId = Integer.valueOf(request.getHeader("id"));        String name = files.getOriginalFilename().replaceAll(" ","");        //获取当前目录下的所有文件,用来判断是否已经存在        List<MyFile> myFiles = null;        if (folderId == 0){            //当前目录为根目录            myFiles = myFileService.getRootFilesByFileStoreId(loginUser.getFileStoreId());        }else {            //当前目录为其他目录            myFiles = myFileService.getFilesByParentFolderId(folderId);        }        for (int i = 0; i < myFiles.size(); i++) {            if ((myFiles.get(i).getMyFileName()+myFiles.get(i).getPostfix()).equals(name)){                logger.error("当前文件已存在!上传失败...");                map.put("code", 501);                return map;            }        }        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");        String dateStr = format.format(new Date());        String path = loginUser.getUserId()+"/"+dateStr +"/"+folderId;        if (!checkTarget(name)){            logger.error("上传失败!文件名不符合规范...");            map.put("code", 502);            return map;        }        Integer sizeInt = Math.toIntExact(files.getSize() / 1024);        //是否仓库放不下该文件        if(store.getCurrentSize()+sizeInt > store.getMaxSize()){            logger.error("上传失败!仓库已满。");            map.put("code", 503);            return map;        }        //处理文件大小        String size = String.valueOf(files.getSize()/1024.0);        int indexDot = size.lastIndexOf(".");        size = size.substring(0,indexDot);        int index = name.lastIndexOf(".");        String tempName = name;        String postfix = "";        int type = 4;        if (index!=-1){            tempName = name.substring(index);            name = name.substring(0,index);            //获得文件类型            type = getType(tempName.toLowerCase());            postfix = tempName.toLowerCase();        }        try {            //提交到FTP服务器            boolean b = FtpUtil.uploadFile("/"+path, name + postfix, files.getInputStream());            if (b){                //上传成功                logger.info("文件上传成功!"+files.getOriginalFilename());                //向数据库文件表写入数据                myFileService.addFileByFileStoreId(                        MyFile.builder()                              .myFileName(name).fileStoreId(loginUser.getFileStoreId()).myFilePath(path)                              .downloadTime(0).uploadTime(new Date()).parentFolderId(folderId).                              size(Integer.valueOf(size)).type(type).postfix(postfix).build());                //更新仓库表的当前大小                fileStoreService.addSize(store.getFileStoreId(),Integer.valueOf(size));                try {                    Thread.sleep(5000);                    map.put("code", 200);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }else{                logger.error("文件上传失败!"+files.getOriginalFilename());                map.put("code", 504);            }        } catch (IOException e) {            e.printStackTrace();        }        return map;    }     /**     * @Description 网盘的文件下载      * @Author xw     * @Date 23:13 2020/2/10     * @Param [fId]     * @return void     **/原文链接:https://blog.csdn.net/m0_66863468/article/details/122853352
  • [技术干货] Java项目:在线嘿嘿网盘系统设计和实现(java+Springboot+ssm+mysql+maven)[转载 上]
    一、项目简述功能:用户的邮箱注册、验证码验证以及用户登录。 不需要注册账号,也可以上传满足条件的临时文件,但是 只4小时内有效。 文件的管理,上传、下载、重命名、删除、查看统计数 据、分类管理等。 文件夹的管理,创建、删除、重命名。 文件的分享,支持通过链博口二维码的分享方式等等。二、项目运行环境配置: Jdk1.8 + Tomcat8.5 + mysql + Eclispe (IntelliJ IDEA,Eclispe,MyEclispe,Sts 都支持)项目技术: JSP +Springboot+ SpringMVC + MyBatis + ThymeLeaf + FTP+ JavaScript + JQuery + Ajax + maven等等管理员控制器:/** * @ClassName: AdminController * @Description: 管理员控制器 **/@Controllerpublic class AdminController extends BaseController {    private Logger logger = LogUtils.getInstance(AdminController.class);        /**     * @Description 前往用户管理页面     * @Author xw     * @Date 15:11 2020/3/10     * @Param [map]     * @return java.lang.String     **/    @GetMapping("/manages-users")    public String manageUsers(Map<String,Object> map,Integer cur){        if (loginUser.getRole() == 1){            //用于无访问权限            logger.error("当前登录用户:"+loginUser.getUserName()+"无管理员权限!");            return "redirect:/error401Page";        }        //获取全部的用户        Integer usersCount = userService.getUsersCount();        //获取当前查询的页数,如果为空,默认为0        cur = (cur == null || cur<0)?0:cur;        //获得统计信息        FileStoreStatistics statistics = myFileService.getCountStatistics(loginUser.getFileStoreId());        //分页获得20个用户信息        Page<Object> page = PageHelper.startPage(cur, 20);        List<UserToShow> users = userService.getUsers();        map.put("statistics", statistics);        map.put("users", users);        map.put("page", page);        map.put("usersCount", usersCount);        logger.info("用户管理域的内容:"+map);        return "admin/manage-users";    }     /**     * @Description 修改用户的权限和最大容量     * @Author xw     * @Date 18:20 2020/3/10     * @Param [uId, pre, size]     * @return java.lang.String     **/    @GetMapping("/updateStoreInfo")    @ResponseBody    public String updateStoreInfo(Integer uId,Integer pre,Integer size){        Integer integer = fileStoreService.updatePermission(uId, pre, size*1024);        if (integer == 1) {            //更新成功,返回200状态码            logger.info("修改用户"+userService.queryById(uId).getUserName()+":的权限和仓库大小成功!");            return "200";        }else {            //更新失败,返回500状态码            logger.error("修改用户"+userService.queryById(uId).getUserName()+":的权限和仓库大小失败!");            return "500";        }    }     /**     * @Description 删除用户     * @Author xw     * @Date 18:44 2020/3/10     * @Param [uId, cur]     * @return java.lang.String     **/    @GetMapping("/deleteUser")    public String deleteUser(Integer uId,Integer cur){        cur = (cur == null || cur < 0)?1:cur;        User user = userService.queryById(uId);        FileStore fileStore = fileStoreService.getFileStoreByUserId(uId);        List<FileFolder> folders = fileFolderService.getRootFoldersByFileStoreId(fileStore.getFileStoreId());        //迭代删除文件夹        for (FileFolder f:folders) {            deleteFolderF(f);        }        List<MyFile> files = myFileService.getRootFilesByFileStoreId(fileStore.getFileStoreId());        //删除该用户仓库根目录下的所有文件        for (MyFile f:files) {            String remotePath = f.getMyFilePath();            String fileName = f.getMyFileName()+f.getPostfix();            //从FTP文件服务器上删除文件            boolean b = FtpUtil.deleteFile("/"+remotePath, fileName);            if (b){                //删除成功,返回空间                fileStoreService.subSize(f.getFileStoreId(),Integer.valueOf(f.getSize()));                //删除文件表对应的数据                myFileService.deleteByFileId(f.getMyFileId());            }            logger.info("删除文件成功!"+f);        }        if (FtpUtil.deleteFolder("/" + uId)){            logger.info("清空FTP上该用户的文件成功");        }else {            logger.error("清空FTP上该用户的文件失败");        }        userService.deleteById(uId);        fileStoreService.deleteById(fileStore.getFileStoreId());        return "redirect:/manages-users?cur="+cur;    }     /**     * @Description 迭代删除文件夹里面的所有文件和子文件夹     * @Author xw     * @Date 9:17 2020/2/29     * @Param [folder]     * @return void     **/    public void deleteFolderF(FileFolder folder){        //获得当前文件夹下的所有子文件夹        List<FileFolder> folders = fileFolderService.getFileFolderByParentFolderId(folder.getFileFolderId());        //删除当前文件夹的所有的文件        List<MyFile> files = myFileService.getFilesByParentFolderId(folder.getFileFolderId());        if (files.size()!=0){            for (int i = 0; i < files.size(); i++) {                Integer fileId = files.get(i).getMyFileId();                boolean b = FtpUtil.deleteFile("/"+files.get(i).getMyFilePath(), files.get(i).getMyFileName() + files.get(i).getPostfix());                if (b){                    myFileService.deleteByFileId(fileId);                    fileStoreService.subSize(folder.getFileStoreId(),Integer.valueOf(files.get(i).getSize()));                }            }        }        if (folders.size()!=0){            for (int i = 0; i < folders.size(); i++) {                deleteFolderF(folders.get(i));            }        }        fileFolderService.deleteFileFolderById(folder.getFileFolderId());    }}登录控制器:/** * @Description 登录控制器 * @return **/@Controllerpublic class LoginController extends BaseController {     private Logger logger = LogUtils.getInstance(LoginController.class);     /**     * @Description 免登陆用户入口,用于本地开发测试,上线运营为了安全请删除此方法     * @Author xw     * @Date 15:17 2020/2/26     * @Param []     * @return java.lang.String     **/    @GetMapping("/admin")    public String adminLogin(){        User user = userService.getUserByOpenId("123456");        logger.info("使用免登陆方式登录成功!"+user);        session.setAttribute("loginUser", user);        return "redirect:/index";    }     /**     * 用于注册流程,用户名,密码,邮箱等校验工作由前端来完成     *     * @param map 用于存储提示信息     * @author GGBOY     * @date 2020/1/28     */    @PostMapping("/register")    public String register(User user, String code, Map<String, Object> map) {        String uCode = (String) session.getAttribute(user.getEmail() + "_code");        if (!code.equals(uCode)) {            map.put("errorMsg", "验证码错误");            return "index";        }        // 用户名去空格        user.setUserName(user.getUserName().trim());        user.setImagePath("https://p.qpic.cn/qqconnect/0/app_101851241_1582451550/100?max-age=2592000&t=0");        user.setRegisterTime(new Date());        user.setRole(1);        if (userService.insert(user)) {            FileStore store = FileStore.builder().userId(user.getUserId()).currentSize(0).build();            fileStoreService.addFileStore(store);            user.setFileStoreId(store.getFileStoreId());            userService.update(user);            logger.info("注册用户成功!当前注册用户" + user);            logger.info("注册仓库成功!当前注册仓库" + store);        } else {            map.put("errorMsg", "服务器发生错误,注册失败");            return "index";        }        session.removeAttribute(user.getEmail() + "_code");        session.setAttribute("loginUser", user);        return "redirect:/index";    }     /**     * 用户登录     * @param map 存储提示信息     * @return java.lang.String     * @author 莫提     * @date 2020/1/28     */    @PostMapping("/login")    public String login(User user, Map<String, Object> map) {        User userByEmail = userService.getUserByEmail(user.getEmail());        if (userByEmail != null && userByEmail.getPassword().equals(user.getPassword())) {            session.setAttribute("loginUser", userByEmail);            logger.info("登录成功!"+userByEmail);            return "redirect:/index";        }else{            User user1 = userService.getUserByEmail(user.getEmail());            String errorMsg = user1 == null ? "该邮箱尚未注册" : "密码错误";            logger.info("登录失败!请确认邮箱和密码是否正确!");            //登录失败,将失败信息返回前端渲染            map.put("errorMsg", errorMsg);            return "index";        }    }     /**     * @return void     * @Description 向注册邮箱发送验证码, 并验证邮箱是否已使用     * @Author xw     * @Date 19:32 2020/1/29     * @Param [userName, email, password]     **/    @ResponseBody    @RequestMapping("/sendCode")    public String sendCode(String userName, String email, String password) {        User userByEmail = userService.getUserByEmail(email);        if (userByEmail != null) {            logger.error("发送验证码失败!邮箱已被注册!");            return "exitEmail";        }        logger.info("开始发送邮件.../n" + "获取的到邮件发送对象为:" + mailSender);        mailUtils = new MailUtils(mailSender);        String code = "123456";        session.setAttribute(email + "_code", code);        return "success";    }     /**     * @Description 请求QQ登录     * @Author xw     * @Date 18:27 2020/2/25     * @Param []     * @return void     **/    @GetMapping("/loginByQQ")    public void login() {        response.setContentType("text/html;charset=utf-8");        try {            response.sendRedirect(new Oauth().getAuthorizeURL(request));            logger.info("请求QQ登录,开始跳转...");        } catch (QQConnectException | IOException e) {            e.printStackTrace();        }    }     /**     * @Description QQ登录回调地址     * @Author xw     * @Date 18:27 2020/2/25     * @Param []     * @return java.lang.String     **/    @GetMapping("/connection")    public String connection() {        try {            AccessToken accessTokenObj = (new Oauth()).getAccessTokenByRequest(request);            String accessToken = null, openID = null;            long tokenExpireIn = 0L;            if ("".equals(accessTokenObj.getAccessToken())) {                logger.error("登录失败:没有获取到响应参数");                return "accessTokenObj=>" + accessTokenObj + "; accessToken" + accessTokenObj.getAccessToken();            } else {                accessToken = accessTokenObj.getAccessToken();                tokenExpireIn = accessTokenObj.getExpireIn();                logger.error("accessToken" + accessToken);                request.getSession().setAttribute("demo_access_token", accessToken);                request.getSession().setAttribute("demo_token_expirein", String.valueOf(tokenExpireIn));                // 利用获取到的accessToken 去获取当前用的openid -------- start                OpenID openIDObj = new OpenID(accessToken);                openID = openIDObj.getUserOpenID();                UserInfo qzoneUserInfo = new UserInfo(accessToken, openID);                UserInfoBean userInfoBean = qzoneUserInfo.getUserInfo();                if (userInfoBean.getRet() == 0) {                    logger.info("用户的OPEN_ID: " + openID);                    logger.info("用户的昵称: " + removeNonBmpUnicode(userInfoBean.getNickname()));                    logger.info("用户的头像URI: " + userInfoBean.getAvatar().getAvatarURL100());                    //设置用户信息                    User user = userService.getUserByOpenId(openID);                    if (user == null){                        user = User.builder()                                .openId(openID).userName(removeNonBmpUnicode(userInfoBean.getNickname()))                                .imagePath(userInfoBean.getAvatar().getAvatarURL100()).                                registerTime(new Date()).build();                        if (userService.insert(user)){                            logger.info("注册用户成功!当前注册用户" + user);                            FileStore store = FileStore.builder().userId(user.getUserId()).build();                            if (fileStoreService.addFileStore(store) == 1){                                user.setFileStoreId(store.getFileStoreId());                                userService.update(user);                                logger.info("注册仓库成功!当前注册仓库" + store);                            }                        } else {                            logger.error("注册用户失败!");                        }                    }else {                        user.setUserName(removeNonBmpUnicode(userInfoBean.getNickname()));                        user.setImagePath(userInfoBean.getAvatar().getAvatarURL100());                        userService.update(user);                    }                    logger.info("QQ用户登录成功!"+user);                    session.setAttribute("loginUser", user);                    return "redirect:/index";                } else {                    logger.error("很抱歉,我们没能正确获取到您的信息,原因是: " + userInfoBean.getMsg());                }            }        } catch (QQConnectException e) {        } finally {            logger.error("登录成功!");        }        return "登录失败!请查看日志信息...";    }     /**     * @Description 处理掉QQ网名中的特殊表情     * @Author xw     * @Date 18:26 2020/2/25     * @Param [str]     * @return java.lang.String 返回处理之后的网名     **/    public String removeNonBmpUnicode(String str) {        if (str == null) {            return null;        }        str = str.replaceAll("[^\\u0000-\\uFFFF]", "");        if ("".equals(str)) {            str = "($ _ $)";        }        return str;    }     /**     * @Description 退出登录,清空session     * @Author xw     * @Date 18:26 2020/2/25     * @Param []     * @return java.lang.String     **/    @GetMapping("/logout")    public String logout() {        logger.info("用户退出登录!");        session.invalidate();        return "redirect:/";    } }文件仓库控制器:/** * @ClassName: FileStoreController * @Description: 文件仓库控制器 **/@Controllerpublic class FileStoreController extends BaseController {    private Logger logger = LogUtils.getInstance(FileStoreController.class);     /**     * @Description 上传临时文件     * @Author xw     * @Date 23:14 2020/3/9     * @Param [files]     * @return void     **/    @PostMapping("/uploadTempFile")   原文链接:https://blog.csdn.net/m0_66863468/article/details/122853352
  • [技术干货] 基于Java+Jsp+SpringMVC漫威手办商城系统设计和实现[转载]
    一、前言介绍:         随着社会的快速发展,计算机的影响是全面且深入的。人们生活水平的不断提高,日常生活中用户对手办周边商城方面的要求也在不断提高,而漫威系列手办周边商城得到广大用户的青睐,使得漫威系列手办周边商城的开发成为必需而且紧迫的事情。漫威系列手办周边商城主要是借助计算机,通过对漫威系列手办周边商城所需的信息管理,增加用户的选择,同时也方便对广大用户信息的及时查询、修改以及对用户信息的及时了解。手办周边商城对用户带来了更多的便利,该系统通过和数据库管理系统软件协作来满足用户的需求。计算机技术在现代管理中的应用,使计算机成为人们应用现代技术的重要工具。能够有效的解决获取信息便捷化、全面化的问题,提高效率。 二、功能设计:       本漫威系列手办周边商城系统主要功能设计为实现管理员;个人中心、系统公告管理、用户管理、商品系列管理、商品信息管理、订单评价管理、论坛管理、系统管理、订单管理,用户;个人中心、订单评价管理、我的收藏管理、订单管理,前台首页;首页、商品信息、论坛信息、我的、跳转到后台、购物车客服等信息管理功能。系统操作流程如下:三、功能截图: 普通用户登录注册: 首页主要功能介绍:商品信息、论坛信息、我的、跳转到后台、购物车客服等信息管理功能 商品详情:可加入购物车、修改数量、立即购买和收藏商品商品详情可以配置图文介绍等 商品评论交流模块 添加了论坛帖子系列功能:普通用户可以发布、可以对帖子进行交流讨论等 帖子详情数据查看、可以在底部评论 在我的个人中心可以查看我的发布、我的订单、我的地址修改以及我的收藏模块和个人信息模块 购物车模块:要选择收货地址信息等 我的订单模块: 我的收藏模块:普通用户后台中心: 订单评价: 收藏管理: 订单信息管理:四、 后端主要功能:修改密码: 用户管理:  商品系列管理: 商品信息管理: 商品详情修改: 商品评价管理: 论坛管理:客服管理: 首页轮播图管理: 订单信息管理:五、数据库设计:表address (地址)编号名称数据类型长度小数位允许空值主键1idbigint200NY2addtimetimestamp190NN3useridbigint200NN4addressvarchar2000NN5namevarchar2000NN6phonevarchar2000NN7isdefaultvarchar2000NN表cart (购物车表)编号名称数据类型长度小数位允许空值主键1idbigint200NY2addtimetimestamp190NN3tablenamevarchar2000YN4useridbigint200NN5goodidbigint200NN6goodnamevarchar2000YN7picturevarchar2000YN8buynumberint100NN9pricefloat130YN10discountpricefloat130YN表chat (客服聊天表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3useridbigint200NN用户id4adminidbigint200YN管理员id5asklongtext21474836470YN提问6replylongtext21474836470YN回复7isreplyint100YN是否回复表config (配置文件)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2namevarchar1000NN配置参数名称3valuevarchar1000YN配置参数值表dingdanpingjia (订单评价)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3dingdanbianhaovarchar2000NN订单编号4shangpinbianhaovarchar2000YN商品编号5shangpinmingchengvarchar2000NN商品名称6shangpinxilievarchar2000YN商品系列7pingfenvarchar2000YN评分8pingjianeironglongtext21474836470YN评价内容9tianjiatupianvarchar2000YN添加图片10pingjiariqidate100YN评价日期11yonghumingvarchar2000YN用户名12lianxidianhuavarchar2000YN联系电话13sfshvarchar2000YN是否审核14shhflongtext21474836470YN审核回复表discussshangpinxinxi (商品信息评论表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3refidbigint200NN关联表id4useridbigint200NN用户id5contentlongtext21474836470NN评论内容6replylongtext21474836470YN回复内容表discussxitonggonggao (系统公告评论表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3refidbigint200NN关联表id4useridbigint200NN用户id5contentlongtext21474836470NN评论内容6replylongtext21474836470YN回复内容表forum (论坛表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3titlevarchar2000YN帖子标题4contentlongtext21474836470NN帖子内容5parentidbigint200YN父节点id6useridbigint200NN用户id7usernamevarchar2000YN用户名8isdonevarchar2000YN状态表orders (订单)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3orderidvarchar2000NN订单编号4tablenamevarchar2000YN商品表名5useridbigint200NN用户id6goodidbigint200NN商品id7goodnamevarchar2000YN商品名称8picturevarchar2000YN商品图片9buynumberint100NN购买数量10pricefloat130NN价格/积分11discountpricefloat130YN折扣价格12totalfloat130NN总价格/总积分13discounttotalfloat130YN折扣总价格14typeint100YN支付类型15statusvarchar2000YN状态16addressvarchar2000YN地址表shangpinxilie (商品系列)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3shangpinxilievarchar2000NN商品系列表shangpinxinxi (商品信息)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3shangpinbianhaovarchar2000YN商品编号4shangpinmingchengvarchar2000NN商品名称5shangpinxilievarchar2000YN商品系列6tupianvarchar2000YN图片7guigevarchar2000YN规格8shangpinxiangqinglongtext21474836470YN商品详情9clicktimedatetime190YN最近点击时间10clicknumint100YN点击次数11pricefloat130NN价格表storeup (收藏表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3useridbigint200NN用户id4refidbigint200YN收藏id5tablenamevarchar2000YN表名6namevarchar2000NN收藏名称7picturevarchar2000NN收藏图片表token (token表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2useridbigint200NN用户id3usernamevarchar1000NN用户名4tablenamevarchar1000YN表名5rolevarchar1000YN角色6tokenvarchar2000NN密码7addtimetimestamp190NN新增时间8expiratedtimetimestamp190NN过期时间表users (用户表)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2usernamevarchar1000NN用户名3passwordvarchar1000NN密码4rolevarchar1000YN角色5addtimetimestamp190NN新增时间表xitonggonggao (系统公告)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3gonggaobiaotivarchar2000NN公告标题4gonggaoleixingvarchar2000NN公告类型5tupianvarchar2000YN图片6neironglongtext21474836470YN内容7faburiqidate100YN发布日期表yonghu (用户)编号名称数据类型长度小数位允许空值主键说明1idbigint200NY主键2addtimetimestamp190NN创建时间3yonghumingvarchar2000NN用户名4mimavarchar2000NN密码5xingmingvarchar2000NN姓名6xingbievarchar2000YN性别7touxiangvarchar2000YN头像8lianxidianhuavarchar2000YN联系电话9moneyfloat130YN余额六、关键代码: /** * 登录相关 */@RequestMapping("users")@RestControllerpublic class UserController{        @Autowired    private UserService userService;        @Autowired    private TokenService tokenService;     /**     * 登录     */    @IgnoreAuth//    @PostMapping(value = "/login")    @RequestMapping(value = "/login", method ={RequestMethod.GET,RequestMethod.POST} )    public R login(String username, String password, String captcha, HttpServletRequest request) {        UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", username));        if(user==null || !user.getPassword().equals(password)) {            return R.error("账号或密码不正确");        }        String token = tokenService.generateToken(user.getId(),username, "users", user.getRole());        return R.ok().put("token", token);    }        /**     * 注册     */    @IgnoreAuth    @PostMapping(value = "/register")    public R register(@RequestBody UserEntity user){//        ValidatorUtils.validateEntity(user);        if(userService.selectOne(new EntityWrapper<UserEntity>().eq("username", user.getUsername())) !=null) {            return R.error("用户已存在");        }        userService.insert(user);        return R.ok();    }     /**     * 退出     */    @GetMapping(value = "logout")    public R logout(HttpServletRequest request) {        request.getSession().invalidate();        return R.ok("退出成功");    }        /**     * 密码重置     */    @IgnoreAuth    @RequestMapping(value = "/resetPass")    public R resetPass(String username, HttpServletRequest request){        UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("username", username));        if(user==null) {            return R.error("账号不存在");        }        user.setPassword("123456");        userService.update(user,null);        return R.ok("密码已重置为:123456");    }        /**     * 列表     */    @RequestMapping("/page")    public R page(@RequestParam Map<String, Object> params,UserEntity user){        EntityWrapper<UserEntity> ew = new EntityWrapper<UserEntity>();        PageUtils page = userService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.allLike(ew, user), params), params));        return R.ok().put("data", page);    }     /**     * 列表     */    @RequestMapping("/list")    public R list( UserEntity user){           EntityWrapper<UserEntity> ew = new EntityWrapper<UserEntity>();          ew.allEq(MPUtil.allEQMapPre( user, "user"));         return R.ok().put("data", userService.selectListView(ew));    }     /**     * 信息     */    @RequestMapping("/info/{id}")    public R info(@PathVariable("id") String id){        UserEntity user = userService.selectById(id);        return R.ok().put("data", user);    }        /**     * 获取用户的session用户信息     */    @RequestMapping("/session")    public R getCurrUser(HttpServletRequest request){        Long id = (Long)request.getSession().getAttribute("userId");        UserEntity user = userService.selectById(id);        return R.ok().put("data", user);    }     /**     * 保存     */    @PostMapping("/save")    public R save(@RequestBody UserEntity user){//        ValidatorUtils.validateEntity(user);        if(userService.selectOne(new EntityWrapper<UserEntity>().eq("username", user.getUsername())) !=null) {            return R.error("用户已存在");        }        userService.insert(user);        return R.ok();    }     /**     * 修改     */    @RequestMapping("/update")    public R update(@RequestBody UserEntity user){//        ValidatorUtils.validateEntity(user);        userService.updateById(user);//全部更新        return R.ok();    }     /**     * 删除     */    @RequestMapping("/delete")    public R delete(@RequestBody Long[] ids){        userService.deleteBatchIds(Arrays.asList(ids));        return R.ok();    }}<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:tx="http://www.springframework.org/schema/tx"       xmlns:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/tx       http://www.springframework.org/schema/tx/spring-tx.xsd       http://www.springframework.org/schema/aop       http://www.springframework.org/schema/aop/spring-aop.xsd">     <!-- 配置数据源 -->    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">        <property name="url" value="${jdbc_url}"/>        <property name="username" value="${jdbc_username}"/>        <property name="password" value="${jdbc_password}"/>         <!-- 初始化连接大小 -->        <property name="initialSize" value="0"/>        <!-- 连接池最大使用连接数量 -->        <property name="maxActive" value="20"/>        <!-- 连接池最大空闲 -->        <property name="maxIdle" value="20"/>        <!-- 连接池最小空闲 -->        <property name="minIdle" value="0"/>        <!-- 获取连接最大等待时间 -->        <property name="maxWait" value="60000"/>         <property name="validationQuery" value="${validationQuery}"/>        <property name="testOnBorrow" value="false"/>        <property name="testOnReturn" value="false"/>        <property name="testWhileIdle" value="true"/>         <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->        <property name="timeBetweenEvictionRunsMillis" value="60000"/>        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->        <property name="minEvictableIdleTimeMillis" value="25200000"/>         <!-- 打开removeAbandoned功能 -->        <property name="removeAbandoned" value="true"/>        <!-- 1800秒,也就是30分钟 -->        <property name="removeAbandonedTimeout" value="1800"/>        <!-- 关闭abanded连接时输出错误日志 -->        <property name="logAbandoned" value="true"/>         <!-- 监控数据库 -->        <property name="filters" value="mergeStat"/>    </bean>     <!-- Spring整合Mybatis,更多查看文档:http://mp.baomidou.com -->    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">        <property name="dataSource" ref="dataSource"/>        <!-- 自动扫描Mapping.xml文件 -->        <property name="mapperLocations" value="classpath:mapper/*.xml"/>        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>        <property name="typeAliasesPackage" value="com..model.*"/>        <property name="typeEnumsPackage" value="com.model.enums"/>        <property name="plugins">            <array>                <!-- 分页插件配置 -->                <bean id="paginationInterceptor" class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">                </bean>            </array>        </property>        <!-- 全局配置注入 -->        <property name="globalConfig" ref="globalConfig" />    </bean>    <bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">        <!--            AUTO->`0`("数据库ID自增")             INPUT->`1`(用户输入ID")            ID_WORKER->`2`("全局唯一ID")            UUID->`3`("全局唯一ID")        -->        <property name="idType" value="2" />        <!--            MYSQL->`mysql`            ORACLE->`oracle`            DB2->`db2`            H2->`h2`            HSQL->`hsql`            SQLITE->`sqlite`            POSTGRE->`postgresql`            SQLSERVER2005->`sqlserver2005`            SQLSERVER->`sqlserver`        -->        <!-- Oracle需要添加该项 -->        <!-- <property name="dbType" value="oracle" /> -->        <!-- 全局表为下划线命名设置 true -->        <!-- <property name="dbColumnUnderline" value="true" /> -->        <property name="metaObjectHandler">            <bean class="com.config.MyMetaObjectHandler" />        </property>    </bean>     <!-- MyBatis 动态扫描  -->    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">        <property name="basePackage" value="com.dao"/>    </bean>     <!-- 配置事务管理 -->    <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"/>    </bean>     <!-- 事务管理 属性 -->    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">        <tx:attributes>            <tx:method name="add*" propagation="REQUIRED"/>            <tx:method name="append*" propagation="REQUIRED"/>            <tx:method name="save*" propagation="REQUIRED"/>            <tx:method name="update*" propagation="REQUIRED"/>            <tx:method name="modify*" propagation="REQUIRED"/>            <tx:method name="edit*" propagation="REQUIRED"/>            <tx:method name="insert*" propagation="REQUIRED"/>            <tx:method name="delete*" propagation="REQUIRED"/>            <tx:method name="remove*" propagation="REQUIRED"/>            <tx:method name="repair" propagation="REQUIRED"/>             <tx:method name="get*" propagation="REQUIRED" read-only="true"/>            <tx:method name="find*" propagation="REQUIRED" read-only="true"/>            <tx:method name="load*" propagation="REQUIRED" read-only="true"/>            <tx:method name="search*" propagation="REQUIRED" read-only="true"/>            <tx:method name="datagrid*" propagation="REQUIRED" read-only="true"/>             <tx:method name="*" propagation="REQUIRED"/>        </tx:attributes>    </tx:advice>     <!-- 配置切面 -->    <aop:config>        <aop:pointcut id="transactionPointcut" expression="execution(* com.service..*.*(..))"/>        <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice"/>    </aop:config> </beans>七、论文报告: 八、源码获取:大家点赞、收藏、关注、评论啦 、查看微信公众号获取联系方式打卡 文章 更新 200/  365天 精彩专栏推荐订阅:在下方专栏Java项目精品实战案例《100套》web前端期末大作业网页实战《100套》原文链接:https://blog.csdn.net/weixin_39709134/article/details/122967896
  • [技术干货] 再见收费的Navicat,操作所有数据库就靠它了[转载]
    作为一名开发者,免不了要和数据库打交道,于是我们就需要一款顺手的数据库管理工具。很长一段时间里,Navicat 都是我的首选,但最近更换了一台新电脑,之前的绿色安装包找不到了。于是就琢磨着,找一款免费的,功能和 Navicat 有一拼的数据库管理工具来替代。好朋友 macrozheng 给我推荐了 DBeaver,试用完后体验真心不错,于是就来给大家安利一波。一、关于 DBeaverDBeaver 是一个跨平台的数据库管理工具,支持 Windows、Linux 和 macOS。它有两个版本,企业版和社区版,对于个人开发者来说,社区版的功能已经足够强大。DBeaver 是由 Java 编写的,默认使用 JDK 11 进行编译。社区版基于 Apache-2.0 License 在 GitHub 上开源,目前已获得 24k+ 的星标。https://github.com/dbeaver/dbeaverDBeaver 支持几乎所有主流的数据库,包括关系型数据库和非关系数据库。二、安装 DBeaver可以通过 DBeaver 官方下载安装包,也可以通过 GitHub 下载 release 版本。官方下载地址:https://dbeaver.io/download/根据自己电脑的操作系统下载对应的安装包,完整安装后,第一步要做的是配置 Maven 镜像,否则在后续下载数据库驱动的时候会非常的慢。因为 DBeaver 是基于 Maven 构建的,数据库驱动也就是链接数据库的 JDBC 驱动是通过 Maven 仓库下载的。选择「首选项」→「Maven」,添加阿里云镜像地址:http://maven.aliyun.com/nexus/content/groups/public和配置 Maven 镜像一样,如下图所示。配置完成后,记得把阿里云镜像仓库置顶。三、管理数据源像使用 Navicat 一样,我们需要先建立连接,这里就以 MySQL 为例。点击「连接」小图标,选择数据库。点击下一步,这时候需要填写数据库连接信息。点击「测试链接」,如果使用默认的 Maven 仓库时,下载驱动会非常慢,如下图所示,还容易失败「踩过的坑就不要再踩了」。如果你前面按照我说的配置了阿里云的 Maven 镜像,程序就不一样了,点了「测试链接」,瞬间会弹出「连接已成功」的提示框。链接成功后,就可以看到数据库中的表啊、视图啊、索引啊等等。四、管理表数据库连接成功后,最重要的还是操作表。01、查看表选择一张表,双击后就可以看到表的属性了,可以查看表的列、约束(主键)、外键、索引等等信息。点击「DDL(Data Definition Language,数据定义语言)」可以看到详细的建表语句。点击「数据」可以查看表的数据,底部有「新增」、「修改」、「删除」等行操作按钮。可以在顶部的过滤框中填写筛选条件,然后直接查询结果。如果不想显示某一列的话,可以直接点击「自定义结果集」图表,将某个字段的状态设置为不可见即可。02、新增表在左侧选择「表」,然后右键选择「新建表」即可建表id。之后在右侧列的区域右键,选择「新建列」即可添加字段。比如说我们新建一个主键 ID,如下图所示。在 DBeaver 中,[v] 表示真,[] 表示否。紧接着在「约束」里选择 ID 将其设置为主键。最后点击保存,会弹出一个建表语句的预览框,点击「执行」即可完成表的创建。五、执行 SQL右键数据库表,选择右键菜单中的「SQL 编辑器」可以打开 SQL 编辑面板。然后编辑 SQL 语句,点击运行的小图标就可以查询数据了。这个过程会有语法提示,非常 nice。DBeaver 有一个很亮眼的操作就是,可以直接选中一条结果集,然后右键生成 SQL。比如说 insert 语句,这样再插入一条重复性内容的时候就非常方便了。六、外观配置可以在首选项里对外观进行设置,比如说把主题修改为暗黑色。然后界面就变成了暗黑系。还可以设置字体大小等。从整体的风格来看,DBeaver 和 Eclipse 有些类似,事实上也的确如此,DBeaver 是基于 Eclipse 平台构建的。七、总结总体来说,DBeaver是一款非常优秀的开源数据库管理工具了,功能很全面,日常的开发基本上是够用了。对比收费的 Navicat 和 DataGrip,可以说非常良心了。大家如果遇到收费版不能使用的时候,可以来体验一下社区版 DBeaver。本篇已收录至 GitHub 上星标 1.5k+ star 的开源专栏《Java 程序员进阶之路》,据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点。学 Java,就认准 Java 程序员进阶之路。https://github.com/itwanger/toBeBetterJavaerstar 了这个仓库就等于你拥有了成为了一名优秀 Java 工程师的潜力。也可以戳下面的链接跳转到《Java 程序员进阶之路》的官网网址,开始愉快的学习之旅吧。https://tobebetterjavaer.com/没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。————————————————版权声明:本文为CSDN博主「沉默王二」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qing_gee/article/details/122975576
  • [技术干货] Spring Boot 3.0 M1 发布,正式弃用 Java 8,最低要求 Java 17
    弃用 Java 8,最低要求 Java 17; Java EE 迁移到了 Jakarta EE; 看到这个心理慌的要命........
  • [Java] 鲲鹏ECS+麒麟系统如何安装Java 1.7
    麒麟系统默认的yum源只提供java 1.8,项目要求只能使用Java 1.7,请问如何在麒麟系统上安装java 1.7呢?
  • [交流吐槽] Java中对象操作流
    ObjectOutputStream构造方法ObjectOutputStream(OutputStream out)创建一个写入指定的OutputStream的ObjectOutputStream对象成员方法void writeObject(Object obj)将指定的对象写入ObjectOutputStream示例代码import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class ObjectOutputStreamTest1 {    public static void main(String[] args) throws IOException {        User user1 = new User("tangsan","123456");        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io-practice\\user1.txt"));                oos.writeObject(user1);        oos.close();    }}ObjectInputStream构造方法ObjectInputStream(InputStream in)创建从指定的InputStream读取的ObjectInputStream成员方法Object readObject()从ObjectInputStream读取一个对象示例代码import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class ObjectInputStreamTest1 {    public static void main(String[] args) throws IOException, ClassNotFoundException {        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io-practice\\user1.txt"));        //此处ois读取出来的数据是Object类型的,需要强转为User类型        User user1 = (User) ois.readObject();        //System.out.println(user1.toString());        System.out.println(user1);        ois.close();    }}
  • [互动交流] obs java sdk 可以设置之前上传文件的有效期吗?
    obs java sdk 之前上传有效期为1天,现在想修改已上传的文件为永久有效? 可以吗?
  • [交流吐槽] 关于Java数组的那些事
    数组是相同数据类型的元素的有序集合。为什么说是有序的呢?访问数组元素是通过数组下标进行的,下标从0开始,依次递增直到 length - 1(length为数组长度),所以可以理解为有序的。首先,来谈谈如何初始化数组?两种初始化方法:动态初始化、静态初始化。(这里以一维数组为例)//动态初始化int[] arr1 = new int[9];//声明了一个数组长度为9的一维数组//除了上面的初始化方式,还可以拆分为两步int[] arr2;arr2 = new int [9];//这两种都属于动态初始化,只是方式存在差异//静态初始化,声明数组长度为5的一维数组int[] arr3 = new int[]{1,2,3,4,5};//静态初始化的标准格式//也可以分为两步初始化//int[] arr3;//arr3 = new int[]{1,2,3,4,5};int[] arr4 = {1,2,3,4},5;//静态初始化的省略格式,不能分为两步既然声明了一个数组,那么数组是储存在哪里的呢?当使用"new"关键字创建数组对象后,会在堆中为数组分配相应的内存空间。总的来说,就是当你使用"new"关键字创建一个对象时,就会在堆中为该对象分配相应的内存空间。数组初始化,那么如何访问数组元素进行赋值和获取元素值呢?int[] arr = new int[5];//数组元素的值默认为0//数组元素赋值for(int i = 0; i < 5; i++){arr = 1;//这里就将数组下标为i的元素赋值为1}//既然对数组赋值了,那么如何获取数组元素的值System.out.println(arr);//这样是否会输出数组元素的值呢//答案是不会,这样输出的是数组的内存地址哈希值[I@#########]//方括号代表是数组,大写字母I代表为整型,后面的#则是十六进制数//这里介绍一种循环:for each循环for(int x:arr){System.out.println(x);//这样就能输出数组元素的值了,当然你也可以使用普通的for循环来进行访问//for(int i = 0; i < 5; i++){//System.out.println(arr);//}}以上就是Java数组中关于一维数组的简单介绍。
  • [交流吐槽] Java基础新特性
    Java的发展从1995年开始经历了许多的过程,但是有三个最具有代表性的JDK版本;JDK1.0:标志着java的诞生;JDK1.2:加入了javax.swing组件,这是主要新特性;JDK1.5:标记为tiger,出现了许多一直沿用至今的特性;JDK1.8:Lambda表达式、接口的定义加强已经接触过了一些新特性,例如:自动装箱与拆箱、switch对String的判断支持。
  • [交流吐槽] 用java写Student类的功能
    1)编写一个Java程序片断,以定义一个表示学生的类Student。这个类的属性有“学号”、“班号”、“姓名”、“性别”、“年龄”,方法有“获得学号”、“获得班号”、“获得性别”、“获得姓名”、“获得年龄”。2)为类Student增加一个方法public String toString( ),该方法把Student类的对象的所有属性信息组合成一个字符串以便输出显示。编写一个Java Application程序,创建Student类的对象,并验证新增加的功能。 package xin;import java.util.Scanner;public class Student {    int number;    String classnumber;    int age;    String name;    String sex;       Student(int x,String y,int z,String p){     number = x;     classnumber = y;     age = z;     name = p;    }     int getNumber() {     return number;  //返回学号    }     String getSClassnumber() {     return classnumber;  //返回班号    }     int getAge() {     return age;     //返回年龄    }     String getName() {      return name;    //返回姓名     }     String getSex() {      return sex;   //返回性别     }    public String toString() {      return "学号:"+number+" 班号:"+classnumber+" 姓名:"+name+" 性别:"+sex+" 年龄:"+age;    }   public static void main(String args[]) {   boolean boo,a;   int number1,age1;   String classnumber1,st;   String name1,sex1;   Student stu = new Student(1,"s",1,"xaio");   Scanner reader = new Scanner(System.in);   System.out.println("请输入学号、班号、姓名、性别、年龄:");   stu.number = reader.nextInt();   stu.classnumber = reader.next();   stu.name = reader.next();   stu.sex = reader.next();   stu.age = reader.nextInt();   st = stu.toString();   System.out.println(st);   System.out.println("是否修改该学生信息?");   System.out.println("如果修改则输入true,反之false");    a = reader.nextBoolean();    if(a) {   System.out.println("请输入学号、班号、姓名、性别、年龄:");   number1 = reader.nextInt();   classnumber1 = reader.next();   name1 = reader.next();   sex1 = reader.next();   age1 = reader.nextInt();       System.out.println("学号:"+number1+" 班号:"+classnumber1+     " 姓名:"+name1+" 性别:"+sex1+" 年龄:"+age1);   }   else   {System.out.println("继续执行下一步");}      System.out.println("输入true继续,false退出");        boo = reader.nextBoolean();     System.out.println("*********************");      if(boo) {       main(args);   //函数调用      }      else       System.out.println("程序无法执行!!!!");   }}
  • [知识分享] 【安全系列】升级过log4j,却还没搞懂log4j漏洞的本质?
    >摘要:log4j远程代码漏洞问题被大范围曝光后已经有一段时间了,今天完整讲清JNDI和RMI以及该漏洞的深层原因。本文分享自华为云社区[《升级过log4j,却还没搞懂log4j漏洞的本质?为你完整讲清jndi、rmi以及该漏洞的深层原因!》](https://bbs.huaweicloud.com/blogs/317342?utm_source=csdn&utm_medium=bbs-ex&utm_campaign=other&utm_content=content),作者:breakDraw。 log4j远程代码漏洞问题被大范围曝光后已经有一段时间了。 很多人只能看到一个“弹出一个计算器”的演示,于是内心想着“哦,就是执行任意代码,启动个计算器”,却对这个漏洞的原理不甚了解。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202201/14/103136s8mozzlbwbzp0ebn.png) 而对于java开发应用不是非常深的同学来讲,jndi、rmi更是很陌生的名词。 这里会以不断提问的方式,逐步推进这个问题的解答,一步步揭开这个漏洞的本质,并给出对这个漏洞的思考。 Q:log4j里的”${}“符号是什么?有什么用? A:可以通过${}的方式,打印一些特殊的值到日志中。 例如${hostName}就可以打印主机名 ${java:vm}打印jvm信息 ${thread:threadName}就可以打印线程名 当你把这个值作为日志的参数,就会打印出来这些值而非原参数名字。 可以理解为log4j的功能更强大了,不需要自己写java代码来打印这些信息,直接用一个字符串就能搞定这些打印。 上面这些都是要实现对应的Lookup类才能做的,即要么log4j内置,要么我们自己新增。 Q:上面这个打印本机信息的是漏洞的原因吗?看起来好象可以在机器里执行奇怪的命令?或者查看文件路径? A:不是的。 上面这些lookup,都是事先定义好的一些loopup字符,并不能做任意的事情!而且就算你发了这些${java.vm}啥的,也只能在服务端打印和收集,你作为攻击者,是收集不到这些信息的 真正的原因,是因为log4j支持的${jndi:xxxx},即支持jndi进行lookup来寻找对象并打印。 Q:什么是JNDI? A:JavaNamingandDirectoryInterface(JAVA命名和目录接口) 简单说就是可以通过JNDI,在java环境中用一个名字,去lookup寻找一个东西使用。 例如可以直接在自己的Java环境中配置一个数据库连接,名字叫“java:MySqlDS” 然后别的java进程通过jndi去查找”java:MysqlDs“,接着就会得到一个数据库连接。 这样如果1个机器有多个进程,都要用同一个连接,完全可以修改整个java环境的jndi数据库对象,然后其他进程就能同时生效了。 Connectionconn=null; //Context就是jdni的类 Contextctx=newInitialContext(); //jndi关键方法,通过loopup找一个对象 ObjectdatasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源 DataSourceds=(Datasource)datasourceRef; conn=ds.getConnection(); ...... c.close(); 除了数据库连接,他还支持loopup找dns,可以弄一个dnsContext然后寻找”sun.com“对应的dns对象 使用JNDI进行高级DNS查询 这样log4j里就可以通过${jndi:dns:http://huaweicloud.com}来获取当前机器中http://huaweicloud.com对应的域名对象进行打印,来确认网络请求失败时,是否是dns获取有问题。 这也就是log4j为啥要引入jndi的原因,可以更方便地获取一些可打印的对象进行日志统计。 然而,jndi还支持通过RMI/LDAP+url字符串,来寻找并获取一个远程对象。 这个寻找远程对象的操作,就是此次漏洞的核心问题所在。 这里只讲RMI。LDAP类似,就不再论述。 Q:RMI是什么? A:RMI,RemoteMethodInvocation。 具体含义: - 远程服务器实现具体的Java方法并提供接口 - 客户端本地仅需根据接口类的定义,提供相应的参数即可调用远程方法 在RMI中,实际上就是返回了一个stub(桩)调用对象给客户端,然后客户都用这个stub对象去做远程调用。 这样客户端就不用关心背后网络怎么写的 **甚至不用知道对方服务是什么端口或者ip** 因此也不需要写sokect的一堆方法搞半天了,也避免了总是修改访问的url啥的。 具体过程如下: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202201/14/103240x4pdals7x4zh6c61.png) 1. Server端监听一个端口,这个端口是JVM随机选择的; 2. Client端并不知道Server远程对象的通信地址和端口,但是Stub中包含了这些信息,并封装了底层网络操作; 3. Client端可以调用Stub上的方法; 4. Stub连接到Server端监听的通信端口并提交参数; 5. 远程Server端上执行具体的方法,并返回结果给Stub; 6. Stub返回执行结果给Client端,从Client看来就好像是Stub在本地执行了这个方法一样; Q:RMI客户端不需要关心服务端的监听端口?那客户端从哪里拿到stub对象呢?总不可能凭空生成吧 A:服务端那边可以启动一个RMI注册中心服务RMIRegistry,端口设置为统一的1099,ip也是固定的。 然后当客户端希望拿到某个服务例如订单服务order的stub对象时,就用”order“这个名字到RMI注册中心上去请求这个stub 这样的话,客户端只需要知道RMI注册中心即可,不需要知道其他服务的ip、端口,非常节省管理成本。 服务端代码长这样: //建立一个订单服务通信桩 OrderServerStubstub=newOrderServerStub(); //启动一个RMI注册中心,端口为1099 LocateRegistry.createRegistry(1099); //把OrderServer这个桩,注册到rmi://0.0.0.0:1099/order这个url上 Naming.bind("rmi://0.0.0.0:1099/order",stub); 客户端的代码长这样,可以看到一个loopup就把这个桩找过来了。 然后就能直接调用stub里的queryOrder方法查询订单了! Registryregistry=LocateRegistry.getRegistry("kingx_kali_host",1099); OrderServerStubstub=(OrderServerStub)registry.lookup("hello"); stub.queryOrder("aaa"); ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202201/14/1033334uulsykdoghedp6z.png) Q:那JNDI和RMI又是什么关系?怎么就联系到一起了 A:上面的代码里,可以看到RMI需要自己写一段Java代码执行。 如果以后你不用RMI来存这个通信对象了,而是用LDAP之类的,咋办?难道代码都要重新写然后部署一份吗? 而如果能用JNDI的方式,通过一个小小的字符串,就能拿到,那就简单了。 那么当我需要切换通信对象的获取方式时,切换JDNI里的设置即可。 而RMI正好实现了JNDI的spi接口,以至于能支持用JNDI+字符串去获取对象 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202201/14/103346v0ab67dppmhfoum3.png) 这里贴一下SPI的概念: SPI,全称为ServiceProviderInterface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。 这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制 - 说人话,spi就是框架方提供一个interface接口,然后只要有人在服务的class发现路径下写一个实现类,就能在代码里直接用上。 而log4j里,正好就支持用${jndi:rmi:x.x.x.x:1099/path}的方式进行RMI对象的获取。 log4j开发者可能本意只是方便用jndi获取各种java容器内置对象,没想到忽略了rmi的获取方式。 **这就导致了我们的服务可能会访问黑客部署的RMI服务,获取到一个不可信的远程调用对象。** Q:但是刚才提到,我们只会通过RMI去拿到一个stub, stub里的内容仅仅是通过特定的ip+port去做发送,代码是固定的 再怎么恶意的命令,也只会在**RMI注册中心即黑客的服务器**上执行,怎么就在我这边触发了攻击? 而且这个stub对象的class文件在我们服务器本地并没有,难道不会报classNotFind异常吗? A:某个讲RMI注入的文章里这样说道: RMI服务端除了直接绑定远程对象之外,还可以通过References引用类来绑定一个外部的远程对象(当前名称目录系统之外的对象)。 绑定了Reference之后,服务端会先通过Referenceable.getReference()获取绑定对象的引用,并且在目录中保存。当客户端在lookup()查找这个远程对象时,客户端会获取相应的objectfactory,最终通过factory类将reference转换为具体的对象实例。 - 说人话,就是RMI允许客户端的java环境中没有这个stub对象 - RMI服务端(那个1099端口的服务)他会返回给你一个factory(序列化传过来),让你调用这个factory做转换。而这个可被序列化生成的factory就是问题的根本原因。 整个利用流程如下: 1. 目标代码中调用了InitialContext.lookup(URI),且URI为用户可控; 2. 攻击者控制URI参数为恶意的RMI服务地址,如:rmi://hacker_rmi_server//name; 3. 攻击者RMI服务器向目标返回一个Reference对象,Reference对象中指定某个精心构造的Factory类; 4. 目标在进行lookup()操作时,会动态加载并实例化Factory类,接着调用factory.getObjectInstance()获取外部远程对象实例; 5. 攻击者可以在Factory类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码,达到RCE的效果; Q:那么log4j-core2.15版本又是怎么改的呢? A:限定jndi使用的协议,禁止在jndi中用ldap、rmi去调用一些远端的服务。 # 思考 说实话,这个漏洞影响之所以这么大,就是因为原理太过简单,随便发一段rmi注册中心的demo和客户端调用demo给别人,他就能复现,甚至用这个方式去攻击。 为什么log4j的设计者当时没有考虑到呢? 很大概率可能是因为jndi的spi机制扩展性太强。 也许最初,jndi只支持dns、数据库driver等对象的命名获取 但是后来随着版本更新,JNDP通过SPI机制,支持了RMI、LDAP等实现,而这个是log4j开发者当时没考虑到的。 换句话说,这是**java高可扩展性和安全性的一次冲突**,因此JNDI的调用方式,未来应该会被更加谨慎地使用了。
总条数:2301 到第 页
上滑加载中