SpringBoot 項(xiàng)目模板:擺脫步步搭建
閱讀本文大概需要 16 分鐘。
git clone https://github.com/e-commerce-sample/order-backend
git checkout a443dace
從寫好README開始
項(xiàng)目簡(jiǎn)介:用一兩句話簡(jiǎn)單描述該項(xiàng)目所實(shí)現(xiàn)的業(yè)務(wù)功能;
技術(shù)選型:列出項(xiàng)目的技術(shù)棧,包括語言、框架和中間件等;
本地構(gòu)建:列出本地開發(fā)過程中所用到的工具命令;
領(lǐng)域模型:核心的領(lǐng)域概念,比如對(duì)于示例電商系統(tǒng)來說有Order、Product等;
測(cè)試策略:自動(dòng)化測(cè)試如何分類,哪些必須寫測(cè)試,哪些沒有必要寫測(cè)試;
技術(shù)架構(gòu):技術(shù)架構(gòu)圖;
部署架構(gòu):部署架構(gòu)圖;
外部依賴:項(xiàng)目運(yùn)行時(shí)所依賴的外部集成方,比如訂單系統(tǒng)會(huì)依賴于會(huì)員系統(tǒng);
環(huán)境信息:各個(gè)環(huán)境的訪問方式,數(shù)據(jù)庫連接等;
編碼實(shí)踐:統(tǒng)一的編碼實(shí)踐,比如異常處理原則、分頁封裝等;
FAQ:開發(fā)過程中常見問題的解答。
https://www.thoughtworks.com/radar/techniques/lightweight-architecture-decision-records
一鍵式本地構(gòu)建
生成IDE工程:idea.sh,生成IntelliJ工程文件并自動(dòng)打開IntelliJ
本地運(yùn)行:run.sh,本地啟動(dòng)項(xiàng)目,自動(dòng)啟動(dòng)本地?cái)?shù)據(jù)庫,監(jiān)聽調(diào)試端口5005
本地構(gòu)建:local-build.sh,只有本地構(gòu)建成功才能提交代碼
拉取代碼;
運(yùn)行idea.sh,自動(dòng)打開IntelliJ;
編寫代碼,包含業(yè)務(wù)代碼和自動(dòng)化測(cè)試;
運(yùn)行run.sh,進(jìn)行本地調(diào)試或必要的手動(dòng)測(cè)試(本步驟不是必需);
運(yùn)行l(wèi)ocal-build.sh,完成本地構(gòu)建;
再次拉取代碼,保證local-build.sh成功,提交代碼。
#!/usr/bin/env bash./gradlew clean bootRun
目錄結(jié)構(gòu)
└── order-backend├── gradle // 文件夾,用于放置所有Gradle配置├── src // 文件夾,Java源代碼├── idea.sh //生成IntelliJ工程├── local-build.sh // 提交之前的本地構(gòu)建└── run.sh // 本地運(yùn)行
├── gradle│ ├── checkstyle│ │ ├── checkstyle.gradle│ │ └── checkstyle.xml
基于業(yè)務(wù)分包
├── order│ ├── OrderApplicationService.java│ ├── OrderController.java│ ├── OrderNotFoundException.java│ ├── OrderRepository.java│ ├── OrderService.java│ └── model│ ├── Order.java│ ├── OrderFactory.java│ ├── OrderId.java│ ├── OrderItem.java│ └── OrderStatus.java
└── product├── Product.java├── ProductApplicationService.java├── ProductController.java├── ProductId.java└── ProductRepository.java
└── common├── configuration├── exception├── loggin└── utils
自動(dòng)化測(cè)試分類
單元測(cè)試:核心的領(lǐng)域模型,包括領(lǐng)域?qū)ο?比如Order類),F(xiàn)actory類,領(lǐng)域服務(wù)類等;
組件測(cè)試:不適合寫單元測(cè)試但是又必須測(cè)試的類,比如Repository類,在有些項(xiàng)目中,這種類型測(cè)試也被稱為集成測(cè)試;
API測(cè)試:模擬客戶端測(cè)試各個(gè)API接口,需要啟動(dòng)程序。
sourceSets {componentTest {compileClasspath += sourceSets.main.output + sourceSets.test.outputruntimeClasspath += sourceSets.main.output + sourceSets.test.output}apiTest {compileClasspath += sourceSets.main.output + sourceSets.test.outputruntimeClasspath += sourceSets.main.output + sourceSets.test.output}}
單元測(cè)試:src/test/java
組件測(cè)試:src/componentTest/java
API測(cè)試:src/apiTest/java
apply plugin: 'docker-compose'dockerCompose {useComposeFiles = ['docker/mysql/docker-compose.yml']}bootRun.dependsOn composeUpcomponentTest.dependsOn composeUpapiTest.dependsOn composeUp
https://www.cnblogs.com/CloudTeng/p/3417762.html
日志處理
?protected?void?doFilterInternal(HttpServletRequest?request,HttpServletResponse?response,FilterChain?filterChain)throws ServletException, IOException {//request id in header may come from Gateway, eg. NginxString headerRequestId = request.getHeader(HEADER_X_REQUEST_ID);MDC.put(REQUEST_ID, isNullOrEmpty(headerRequestId) ? newUuid() : headerRequestId);try {filterChain.doFilter(request, response);} finally {clearMdc();}}
ecommerce-order-backend-${ACTIVE_PROFILE} elk.yourdomain.com 6379 whatever ecommerce-ordder-log true redis
異常處理
向客戶端提供格式統(tǒng)一的異常返回
異常信息中應(yīng)該包含足夠多的上下文信息,最好是結(jié)構(gòu)化的數(shù)據(jù)以便于客戶端解析
不同類型的異常應(yīng)該包含唯一標(biāo)識(shí),以便客戶端精確識(shí)別
public abstract class AppException extends RuntimeException {private final ErrorCode code;private final Mapdata = newHashMap(); }
public class OrderNotFoundException extends AppException {public OrderNotFoundException(OrderId orderId) {super(ErrorCode.ORDER_NOT_FOUND, ImmutableMap.of("orderId", orderId.toString()));}}
public final class ErrorDetail {private final ErrorCode code;private final int status;private final String message;private final String path;private final Instant timestamp;private final Mapdata = newHashMap(); }
{requestId: "d008ef46bb4f4cf19c9081ad50df33bd",error: {code: "ORDER_NOT_FOUND",status: 404,message: "沒有找到訂單",path: "/order",timestamp: 1555031270087,data: {orderId: "123456789"}}}
后臺(tái)任務(wù)與分布式鎖
@Configuration@EnableAsync@EnableSchedulingpublic class SchedulingConfiguration implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setScheduler(newScheduledThreadPool(10));}@Bean(destroyMethod = "shutdown")@Primarypublic TaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(2);executor.setMaxPoolSize(5);executor.setQueueCapacity(10);executor.setTaskDecorator(new LogbackMdcTaskDecorator());executor.initialize();return executor;}}
@Configuration@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")public class DistributedLockConfiguration {@Beanpublic LockProvider lockProvider(DataSource dataSource) {return new JdbcTemplateLockProvider(dataSource);}@Beanpublic DistributedLockExecutor distributedLockExecutor(LockProvider lockProvider) {return new DistributedLockExecutor(lockProvider);????}}
@Scheduled(cron = "0 0/1 * * * ?")@SchedulerLock(name = "scheduledTask", lockAtMostFor = THIRTY_MIN, lockAtLeastFor = ONE_MIN)public void run() {logger.info("Run scheduled task.");}為了支持代碼直接調(diào)用分布式鎖,基于Shedlock的LockProvider創(chuàng)建DistributedLockExecutor:public class DistributedLockExecutor {private final LockProvider lockProvider;public DistributedLockExecutor(LockProvider lockProvider) {this.lockProvider = lockProvider;}publicT executeWithLock(Supplier supplier, LockConfiguration configuration) { Optionallock = lockProvider.lock(configuration); if (!lock.isPresent()) {throw new LockAlreadyOccupiedException(configuration.getName());}try {return supplier.get();} finally {lock.get().unlock();}}}
public String doBusiness() {return distributedLockExecutor.executeWithLock(() -> "Hello World.",new LockConfiguration("key", Instant.now().plusSeconds(60)));}
統(tǒng)一代碼風(fēng)格
客戶端的請(qǐng)求數(shù)據(jù)類統(tǒng)一使用相同后綴,比如Command
返回給客戶端的數(shù)據(jù)統(tǒng)一使用相同后綴,比如Represetation
統(tǒng)一對(duì)請(qǐng)求處理的流程框架,比如采用傳統(tǒng)的3層架構(gòu)或者DDD戰(zhàn)術(shù)模式
提供一致的異常返回(請(qǐng)參考“異常處理”小節(jié))
提供統(tǒng)一的分頁結(jié)構(gòu)類
明確測(cè)試分類以及統(tǒng)一的測(cè)試基礎(chǔ)類(請(qǐng)參考“自動(dòng)化測(cè)試分類”小節(jié))
靜態(tài)代碼檢查
Checkstyle:用于檢查代碼格式,規(guī)范編碼風(fēng)格
Spotbugs:Findbugs的繼承者
Dependency check:OWASP提供的Java類庫安全性檢查
Sonar:用于代碼持續(xù)改進(jìn)的跟蹤
健康檢查
我們希望初步檢查程序是否運(yùn)行正常
有些負(fù)載均衡軟件會(huì)通過一個(gè)健康檢查URL判斷節(jié)點(diǎn)的可達(dá)性
./run.sh
{requestId: "698c8d29add54e24a3d435e2c749ea00",buildNumber: "unknown",buildTime: "unknown",deployTime: "2019-04-11T13:05:46.901+08:00[Asia/Shanghai]",gitRevision: "unknown",gitBranch: "unknown",environment: "[local]"}
API文檔
@Configuration@EnableSwagger2@Profile(value = {"local", "dev"})public class SwaggerConfiguration {@Beanpublic Docket api() {return new Docket(SWAGGER_2).select().apis(basePackage("com.ecommerce.order")).paths(any()).build();}}

數(shù)據(jù)庫遷移
resources/├── db│ └── migration│ ├── V1__init.sql│ └── V2__create_product_table.sql
多環(huán)境構(gòu)建
local:用于開發(fā)者本地開發(fā)
ci:用于持續(xù)集成
dev:用于前端開發(fā)聯(lián)調(diào)
qa:用于測(cè)試人員
uat:類生產(chǎn)環(huán)境,用于功能驗(yàn)收(有時(shí)也稱為staging環(huán)境)
prod:正式的生產(chǎn)環(huán)境
CORS
@Configurationpublic class CorsConfiguration {@Beanpublic WebMvcConfigurer corsConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**");}};}}
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {protected void configure(HttpSecurity http) throws Exception {http// by default uses a Bean by the name of corsConfigurationSource.cors().and()...}CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedOrigins(Arrays.asList("https://example.com"));configuration.setAllowedMethods(Arrays.asList("GET","POST"));UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}}
Guava:來自Google的常用類庫
Apache Commons:來自Apache的常用類庫
Mockito:主要用于單元測(cè)試的mock
DBUnit:測(cè)試中管理數(shù)據(jù)庫測(cè)試數(shù)據(jù)
Rest Assured:用于Rest API測(cè)試
Jackson 2:Json數(shù)據(jù)的序列化和反序列化
jjwt:Jwt token認(rèn)證
Lombok:自動(dòng)生成常見Java代碼,比如equals()方法,getter和setter等;
Feign:聲明式Rest客戶端
Tika:用于準(zhǔn)確檢測(cè)文件類型
itext:生成Pdf文件等
zxing:生成二維碼
Xstream:比Jaxb更輕量級(jí)的XML處理庫
總結(jié)
推薦閱讀:
想接私活時(shí)薪再翻一倍,建議根據(jù)這幾個(gè)開源的SpringBoot項(xiàng)目(含小程序)改改~
騰訊,干掉 Redis 項(xiàng)目,正式開源、太牛逼啦!
微信掃描二維碼,關(guān)注我的公眾號(hào)
朕已閱?

