久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

項(xiàng)目之創(chuàng)建靜態(tài)資源和設(shè)置子模塊項(xiàng)目,、開發(fā)簡(jiǎn)易上傳功能(11)

 海擁 2021-11-30

48. 在父項(xiàng)目中管理子模塊項(xiàng)目使用的依賴

在一個(gè)項(xiàng)目中,如果某些依賴只是部分子模塊項(xiàng)目需要使用的,應(yīng)該將這些依賴配置在<dependencyManagement>節(jié)點(diǎn)中,凡配置在這個(gè)節(jié)點(diǎn)中的依賴,任何子模塊項(xiàng)目中都不會(huì)直接擁有,如果某個(gè)子模塊項(xiàng)目需要使用這些依賴,依然需要使用<dependency>節(jié)點(diǎn)來添加!與在子模塊項(xiàng)目中直接添加<denpendency>(父級(jí)的<dependencyManagement>沒有配置某個(gè)依賴而子模塊項(xiàng)目中直接添加)的區(qū)別在于:如果事先使用父級(jí)項(xiàng)目的<dependencyManagement>進(jìn)行了配置,則子模塊項(xiàng)目在添加時(shí),不需要指定版本號(hào),直接使用父級(jí)項(xiàng)目配置的版號(hào),以便于在父級(jí)項(xiàng)目中統(tǒng)一管理依賴的版本

注意:在父級(jí)項(xiàng)目中,添加許多依賴都是不需要指定版本號(hào)的,但是,如果將這些依賴配置到<dependencyManagement>中用于指導(dǎo)子模塊項(xiàng)目所使用的依賴的版本時(shí),必須顯式的指定版本號(hào),否則,子模塊項(xiàng)目將不明確需要使用的是哪個(gè)版本!

則在父級(jí)項(xiàng)目中,關(guān)于依賴的管理:

<properties>
    <!-- Java Version -->
    <java.version>1.8</java.version>
    <!-- Dependency Version -->
    <mysql.version>8.0.20</mysql.version>
    <mybatis.version>2.1.3</mybatis.version>
    <mybatis.plus.version>3.3.2</mybatis.plus.version>
    <druid.version>1.1.23</druid.version>
    <pagehelper.version>1.2.13</pagehelper.version>
    <thymeleaf.springsecurity5.version>3.0.4.RELEASE</thymeleaf.springsecurity5.version>
    <spring.boot.starter.version>2.3.1.RELEASE</spring.boot.starter.version>
    <lombok.version>1.18.12</lombok.version>
</properties>

<!-- 直接添加在dependencies節(jié)點(diǎn)的中的依賴是每個(gè)子模塊項(xiàng)目都直接擁有的 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${spring.boot.starter.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<!-- 添加在dependencyManagement中的依賴只是為了管理子模塊項(xiàng)目使用依賴時(shí)的版本 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>${pagehelper.version}</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            <version>${thymeleaf.springsecurity5.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>${spring.boot.starter.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>${spring.boot.starter.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>${spring.boot.starter.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <version>${lombok.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

由于大量的依賴都已經(jīng)添加在<dependencyManagement>中了,則straw-generatorstraw-portal項(xiàng)目都不會(huì)直接擁有這些依賴,則需要在這2個(gè)子模塊項(xiàng)目中自行添加所需的依賴!

straw-generator項(xiàng)目中(關(guān)于代碼生成器的相關(guān)依賴由于過于特殊,一定只有當(dāng)前項(xiàng)目需要使用,所以,對(duì)版本的管理方式可以不嚴(yán)格要求):

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.3.2</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-extension</artifactId>
        <version>3.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
        <version>2.3.1.RELEASE</version>
    </dependency>
</dependencies>

straw-portal項(xiàng)目中:

<dependencies>
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

50. 創(chuàng)建靜態(tài)資源子模塊項(xiàng)目

創(chuàng)建新的straw-resource子模塊項(xiàng)目,用于管理用戶上傳的文件等靜態(tài)資源。

創(chuàng)建出來后,在straw-resourcepom.xml中,自行將父級(jí)項(xiàng)目由SpringBoot改為straw項(xiàng)目,刪除<dependencies><build>節(jié)點(diǎn)(因?yàn)闆]有存在的必要,在父項(xiàng)目中已經(jīng)配置好了),。

straw項(xiàng)目中的<mudules>中添加子模塊項(xiàng)目,。

straw-resourceapplication.properties中顯式的配置端口號(hào),必須與straw-portal的不同:

server.port=8081

全部完成后,更新Maven,straw-portalstraw-resource這2個(gè)項(xiàng)目是可以同時(shí)啟動(dòng)的!

51. 設(shè)置straw-resource子模塊項(xiàng)目的靜態(tài)目錄

straw-resource項(xiàng)目的application.properties中添加配置:

spring.resources.static-locations=file:D:/IdeaProjects/straw-static-resource

straw-resource項(xiàng)目的靜態(tài)目錄就是以上指定的位置,后續(xù)straw-portal項(xiàng)目中涉及上傳操作時(shí),上傳的文件也應(yīng)該存放到以上位置。

52.設(shè)置straw-resource子模塊項(xiàng)目的靜態(tài)目錄

straw-portal項(xiàng)目的application.properties中添加配置:

# 發(fā)布問題時(shí),將圖片上傳到哪里,需要與straw-resource項(xiàng)目的靜態(tài)資源目錄保持一致
project.question.image-upload-path=D:/IdeaProjects/straw-static-resource

# 發(fā)布問題時(shí),上傳的圖片通過哪個(gè)服務(wù)器提供訪問,配置的端口號(hào)需要與straw-resource項(xiàng)目保持一致
project.question.image-host=http://localhost:8081/

# 發(fā)布問題時(shí),允許上傳的文件的最大大小
project.question.image-max-size=307200

# 發(fā)布問題時(shí),允許上傳的圖片文件的類型
project.question.image-content-types=image/jpeg, image/png, image/bmp

并且,在straw-portal中調(diào)整默認(rèn)限制的文件大小:

@Bean
public MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    factory.setMaxFileSize(DataSize.ofMegabytes(500));
    factory.setMaxRequestSize(DataSize.ofMegabytes(500));
    return factory.createMultipartConfig();
}

53. 開發(fā)簡(jiǎn)易上傳功能

說明:由于上傳功能不可以通過在URL上填寫參數(shù)直接進(jìn)行測(cè)試,為了更快的進(jìn)行測(cè)試并體驗(yàn)上傳的效果,暫且忽略不必要的代碼,例如上傳文件的相關(guān)檢查等細(xì)節(jié)問題,當(dāng)然,測(cè)試時(shí)也應(yīng)該使用正確的文件和數(shù)據(jù)進(jìn)行測(cè)試,。當(dāng)簡(jiǎn)單的上傳已經(jīng)完成后,再補(bǔ)全細(xì)節(jié)部分,。

QuestionController中開發(fā)服務(wù)器端的簡(jiǎn)易上傳處理:

@Value("${project.question.image-upload-path}")
private String imageUploadPath;
@Value(("${project.question.image-host}"))
private String imageHost;

@PostMapping("/upload-image")
public R<String> uploadImage(MultipartFile imageFile) {
    File dest = new File(imageUploadPath, "1.jpg");

    try {
        imageFile.transferTo(dest);
    } catch (IOException e) {
        e.printStackTrace();
    }

    String imageUrl = imageHost + "1.jpg"; // http://localhost:8081/1.jpg
    log.debug("image url >>> {}", imageUrl);

    return R.ok(imageUrl);
}

本次需要處理的頁面是“發(fā)表問題”的question/create.html,在發(fā)表問題時(shí),使用的富文本編輯Summernote提供了名為callbacks的回調(diào)機(jī)制,其中,存在名為onImageUpload的回調(diào)屬性,該屬性值是函數(shù),所以,可以自定義函數(shù)配置到這個(gè)回調(diào)屬性中,則后續(xù)上傳圖片時(shí),就會(huì)自動(dòng)觸發(fā)自定義的函數(shù),通過自定義函數(shù)實(shí)現(xiàn)圖片的上傳,并返回上傳圖片的URL,生成圖片插入到Summernote富文本編輯器中即可。

question/create.html中,先將底部關(guān)于Summernote的JavaScript代碼移到新創(chuàng)建的commons/init_summernote.js中,并調(diào)整這段代碼:

$(document).ready(function () {
    $('#summernote').summernote({
        height: 300,
        tabsize: 2,
        lang: 'zh-CN',
        placeholder: '請(qǐng)輸入問題的詳細(xì)描述...',
        callbacks: {
            onImageUpload: function () {
                alert("準(zhǔn)備上傳圖片!");
            }
        }
    });
});

完成后,重啟項(xiàng)目,打開“發(fā)布問題”頁面,插入圖片,選擇圖片文件就會(huì)彈出對(duì)話框!

然后,在以上回調(diào)中,使用$.ajax()提交異步請(qǐng)求,在處理結(jié)果時(shí),創(chuàng)建Image對(duì)象,將結(jié)果中的圖片URL作為Image對(duì)象的src屬性值,并將整個(gè)Image對(duì)象(就是一個(gè)<src>標(biāo)簽)插入到富文本編輯器中:

$(document).ready(function () {
    $('#summernote').summernote({
        height: 300,
        tabsize: 2,
        lang: 'zh-CN',
        placeholder: '請(qǐng)輸入問題的詳細(xì)描述...',
        callbacks: {
            onImageUpload: function (files) {
                // ---------------------------------------
                // 當(dāng)前函數(shù)的參數(shù)名稱是自定義,它表示用戶選擇的若干個(gè)文件
                // Summernote在調(diào)用該函數(shù)時(shí),會(huì)把用戶選擇的文件作為函數(shù)的參數(shù)
                // ---------------------------------------
                if (!files || files.length < 1) {
                    alert("請(qǐng)選擇您要上傳的文件!");
                    return;
                }
                if (files.length > 1) {
                    alert("一次只允許上傳1個(gè)文件!");
                    return;
                }

                let formData = new FormData();
                let file = files[0];
                formData.append("imageFile", file);
                console.log("form data >>> " + formData);

                $.ajax({
                    url: '/api/v1/questions/upload-image',
                    type: 'post',
                    data: formData,
                    contentType: false,
                    processData: false,
                    success: function(json) {
                        if (json.state == 2000) {
                            // alert(json.data);
                            let img = new Image(); // <img>
                            img.src = json.data; // <img src="xxx">
                            $('#summernote').summernote('insertNode', img);
                        } else {
                            alert(json.message);
                        }
                    }
                });
            }
        }
    });
});

54. 完善服務(wù)器端的上傳功能

先創(chuàng)建關(guān)于文件上傳的異常類型:

public class FileUploadException extends RuntimeException {
}
public class FileEmptyException extends FileUploadException {
}
public class FileSizeException extends FileUploadException {
}
public class FileTypeException extends FileUploadException {
}
public class FileIOException extends FileUploadException {
}

GlobalExceptionHandler中處理以上異常,完整代碼如下(需在R.State中添加常量):

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler
    public R handleException(Throwable e) {
        if (e instanceof ParameterValidationException) {
            return R.failure(R.State.ERR_PARAMETER_INVALIDATION, e);
        } else if (e instanceof InviteCodeException){
            return R.failure(R.State.ERR_INVITE_CODE, e);
        } else if (e instanceof ClassDisabledException) {
            return R.failure(R.State.ERR_CLASS_DISABLED, e);
        } else if (e instanceof PhoneDuplicateException) {
            return R.failure(R.State.ERR_PHONE_DUPLICATE, e);
        } else if (e instanceof InsertException) {
            return R.failure(R.State.ERR_INSERT, e);
        } else if (e instanceof FileEmptyException) {
            return R.failure(R.State.ERR_UPLOAD_EMPTY, e);
        } else if (e instanceof FileSizeException) {
            return R.failure(R.State.ERR_UPLOAD_FILE_SIZE, e);
        } else if (e instanceof FileTypeException) {
            return R.failure(R.State.ERR_UPLOAD_FILE_TYPE, e);
        } else if (e instanceof FileIOException) {
            return R.failure(R.State.ERR_UPLOAD_FILE_IO, e);
        } else if (e instanceof AccessDeniedException) {
            return R.failure(R.State.ERR_ACCESS_DENIED, e);
        } else {
            log.debug("Unknown Exception", e);
            return R.failure(R.State.ERR_UNKNOWN, e);
        }
    }

}

在處理上傳請(qǐng)求之前,先聲明2個(gè)全局屬性,用于讀取配置中的“文件最大大小”和“文件類型”:

@Value("${project.question.image-max-size}")
private long imageMaxSize;
@Value(("${project.question.image-content-types}"))
private List<String> imageContentTypes;

在處理上傳請(qǐng)求的過程中:

  • 應(yīng)該創(chuàng)建子級(jí)文件夾,避免所有的文件都傳到指定的同一個(gè)文件夾中,推薦使用“年”和“月”分別創(chuàng)建2級(jí)子文件夾,上傳的圖片應(yīng)該放在“月”的文件夾中;
  • 可以使用UUID作為文件名;
  • 不需要判斷原始擴(kuò)展名,而是直接從原始文件全名中截取即可;
  • 及時(shí)打樁,輸出關(guān)鍵信息,例如保存文件的文件夾路徑,、文件名,、完整路徑等,便于出錯(cuò)時(shí)排查問題。

具體代碼:

@Value("${project.question.image-upload-path}")
private String imageUploadPath;
@Value(("${project.question.image-host}"))
private String imageHost;
@Value("${project.question.image-max-size}")
private long imageMaxSize;
@Value(("${project.question.image-content-types}"))
private List<String> imageContentTypes;

@PostMapping("/upload-image")
public R<String> uploadImage(MultipartFile imageFile) {
    // 判斷上傳的文件是否為空
    if (imageFile.isEmpty()) {
        throw new FileEmptyException("上傳圖片失敗!請(qǐng)選擇有效的圖片文件!");
    }
    // 判斷上傳的文件大小是否超標(biāo)
    if (imageFile.getSize() > imageMaxSize) {
        throw new FileSizeException("上傳圖片失敗!不允許使用超過" + (imageMaxSize / 1024) + "KB的圖片文件!");
    }
    // 判斷上傳的文件類型是否超標(biāo)
    if (!imageContentTypes.contains(imageFile.getContentType())) {
        throw new FileTypeException("上傳圖片失敗!圖片類型錯(cuò)誤!允許上傳的圖片類型有:" + imageContentTypes);
    }

    // 確定本次上傳時(shí)使用的文件夾
    String dir = DateTimeFormatter.ofPattern("yyyy/MM").format(LocalDateTime.now());
    File parent = new File(imageUploadPath, dir);
    if (!parent.exists()) {
        parent.mkdirs();
    }
    log.debug("dir >>> {}", parent);

    // 確定本次上傳時(shí)使用的文件名
    String filename = UUID.randomUUID().toString();
    String originalFilename = imageFile.getOriginalFilename();
    String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
    String child = filename + suffix;

    // 創(chuàng)建最終保存時(shí)的文件對(duì)象
    File dest = new File(parent, child);

    // 執(zhí)行保存
    try {
        imageFile.transferTo(dest);
    } catch (IOException e) {
        throw new FileIOException("上傳圖片失敗!當(dāng)前服務(wù)器忙,請(qǐng)稍后再次嘗試!");
    }

    // 確定網(wǎng)絡(luò)訪問路徑
    String imageUrl = imageHost + dir + "/" + child; // http://localhost:8081/1.jpg
    log.debug("image url >>> {}", imageUrl);

    // 返回
    return R.ok(imageUrl);
}

55. 顯示老師主頁

老師的主頁文件是index_teacher.html,原本在static文件夾中,先把它拖拽到templates文件夾中,。

SystemController中,修改原有訪問/index.html路徑的處理方法:

@GetMapping("/index.html")
public String index(@AuthenticationPrincipal UserInfo userInfo) {
    if (userInfo.getType() == 0) {
        return "index";
    } else {
        return "index_teacher";
    }
}

需要注意:以上判斷用戶身份時(shí),會(huì)判斷用戶數(shù)據(jù)的type屬性,此前,在UserServiceImpl.login()方法中已經(jīng)向返回的UserInfo中設(shè)置了從數(shù)據(jù)庫(kù)中讀取到的type屬性,則以上代碼可以正常獲取type值!

56. 老師主頁顯示問題列表-持久層

(a) 規(guī)范需要執(zhí)行的SQL語句

老師主頁顯示的問題列表應(yīng)該顯示出老師自己發(fā)表的問題,和學(xué)生指定該老師回答的問題,。

這樣的列表數(shù)據(jù)可以使用此前的QuestionVO來表示每一個(gè)問題的數(shù)據(jù),列表則使用List<QuestionVO>來表示。

需要執(zhí)行的SQL語句大致是:

select question.*
from question
left join user_question
on question.id=user_question.question_id
where question.user_id=? or user_question.user_id=? and is_delete=0
order by status, modified_time desc;

(b) 在接口中添加抽象方法

List<QuestionVO> findTeacherQuestions(Integer teacherId);

? 配置SQL映射


(d) 單元測(cè)試


57. 老師主頁顯示問題列表-業(yè)務(wù)層

(a)

(b) 接口與抽象方法

原本存在抽象方法:

PageInfo<QuestionVO> getQuestionsByUserId(Integer userId, Integer page);

改為:

PageInfo<QuestionVO> getQuestionsByUserId(Integer userId, Integer type, Integer page);

? 實(shí)現(xiàn)業(yè)務(wù)方法

在原本存在的getQuestionsByUserId()方法的參數(shù)列表中添加參數(shù),與以上抽象方法保持一致,然后,在實(shí)現(xiàn)過程中:

// 設(shè)置分頁參數(shù)
PageHelper.startPage(page, pageSize);
// 調(diào)用持久層方法查詢問題列表,該列表中的數(shù)據(jù)只有標(biāo)簽的id,并不包括標(biāo)簽數(shù)據(jù)
List<QuestionVO> questions;
if (type == 0) {
    questions = questionMapper.findListByUserId(userId);
} else {
    questions = questionMapper.findTeacherQuestions(userId);
}
// 后續(xù)代碼不變

(d) 單元測(cè)試

由于修改了業(yè)務(wù)方法的聲明,當(dāng)前控制器層的調(diào)用會(huì)因?yàn)閰?shù)不匹配而報(bào)錯(cuò),將無法進(jìn)行單元測(cè)試,所以,先處理完控制器層再測(cè)試,。

58. 老師主頁顯示問題列表-控制器層

在原來的獲取學(xué)生問題列表的方法中,調(diào)用業(yè)務(wù)方法時(shí)多添加type值即可,該值來自UserInfo參數(shù):

@GetMapping("/my")
public R<PageInfo<QuestionVO>> getMyQuestions(Integer page,
       @AuthenticationPrincipal UserInfo userInfo) {
    if (page == null || page < 1) {
        page = 1;
    }
    PageInfo<QuestionVO> questions = questionService.getQuestionsByUserId(userInfo.getId(), userInfo.getType(), page);
    return R.ok(questions);
}

完成后,應(yīng)該分別測(cè)試學(xué)生賬號(hào)登錄后顯示列表和老師賬號(hào)登錄后顯示列表,。

59. 老師主頁顯示問題列表-前端頁面

引用question/create.html中的處理即可!也就是說:在question/create.html中將列表區(qū)域設(shè)置為th:fragment,然后在index_teacher.html中通過th:replace直接引用即可!

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多