提交 a36fee51 作者: 方治民

refactor: 重构部分代码、依赖更新、新增 ci/文档/配置项等

上级 94cb809d
# public/* linguist-vendored
* text=auto eol=lf
...@@ -35,3 +35,4 @@ out/ ...@@ -35,3 +35,4 @@ out/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
node_modules
# Pipelines 步骤
stages:
- build
- test
- deploy
# 缓存配置
cache:
paths:
- .gradle/wrapper
- .gradle/caches
# 编译项目
build-job:
stage: build
image: java:8
only:
- tags
# 使用 CI Runner,在 GitLab-Runner 中注册好的 Runner
tags:
- CI
before_script:
- chmod +x ./gradlew
script:
- ./gradlew assemble
artifacts:
# 配置构建结果过期时间
expire_in: 1 week
# 保留目录
paths:
- build/libs/*.jar
# 发布,在本地构建镜像并推送到发布环境的镜像库
deploy-job:
stage: deploy
image: docker:latest
# 部署依赖编译
dependencies:
- build-job
only:
- tags
# 使用 CD Runner,在 GitLab-Runner 中注册好的 Runner(此处配置成使用宿主环境构建)
tags:
- CD
script:
# 基于 Dockerfile 构建镜像
- docker build -t $TAG .
# 登录到发布环境的私服
- docker login -u $REGISTRY_REMOTE_USER -p $REGISTRY_REMOTE_PASSWORD https://$REGISTRY_REMOTE
# 将刚刚构建的镜像推送到私服
- docker push $TAG
variables:
# 读取 GitLab CI/CD 配置的 Secret variables
REGISTRY_REMOTE: $REGISTRY_REMOTE
REGISTRY_REMOTE_USER: $REGISTRY_REMOTE_USER
REGISTRY_REMOTE_PASSWORD: $REGISTRY_REMOTE_PASSWORD
# 设置镜像 tag,使用 git tag 标识作为镜像 tag
TAG: ${REGISTRY_REMOTE}/basic/basic-api:${CI_BUILD_REF_NAME}
# 是否需要分号
semi: false
# 单引号
singleQuote: true
# 在对象,数组括号与文字之间加空格 "{ foo: bar }"
bracketSpacing: true
# 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
trailingComma: all
# 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
proseWrap: preserve
# tab
tabWidth: 4
# 一行的字符数,如果超过会进行换行,默认为80
printWidth: 120
# vueIndentScriptAndStyle: true
jsxBracketSameLine: false
jsxSingleQuote: false
htmlWhitespaceSensitivity: strict
endOfLine: lf
# 覆盖配置
overrides:
- files:
- '*.yml'
- '*.styl'
options:
tabWidth: 2
# 指定基础镜像,在其上进行定制
FROM java:8
# 维护者信息
MAINTAINER ifzm <fangzhimin@yiring.com>
# 声明数据挂载目录
VOLUME /data
# 声明日志挂载目录
VOLUME /logs
# 复制上下文目录下的 build/libs/app.jar 到容器里
COPY build/libs/app-0.0.1-SNAPSHOT.jar app.jar
# bash方式执行,使 app.jar 可访问
# RUN新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像。
RUN bash -c "touch /app.jar"
# 声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务
EXPOSE 8181
# 指定容器启动程序及参数 <ENTRYPOINT> "<CMD>"
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]
# Basic API(Template)
## 开发环境
<!-- prettier-ignore -->
- JDK 1.8+ (推荐同时安装最新 LTS 版本) [下载](https://www.oracle.com/java/technologies/downloads/#jdk17-windows)
- NodeJS [latest](https://nodejs.org/zh-cn/)
- IDEA [下载](https://www.jetbrains.com/idea/)
- Navicat Premium [下载](http://www.downcc.com/soft/430673.html)
- RedisDesktopManager [下载](https://github.com/FuckDoctors/rdm-builder)
- Docker(**可选**) [了解](https://www.docker.com/)
- [常用插件清单](./doc/plugins.md)
## 开始
<!-- prettier-ignore -->
- [开发规范说明](./doc/workflow.md)
- [技术栈说明](./doc/technique.md)
---
## TODO
<!-- prettier-ignore -->
- [x] 完成项目构建,开发文档编写
- [x] [conventional-changelog](https://www.cnblogs.com/mengfangui/p/12634845.html)
- [ ] 设计权限模块
...@@ -19,6 +19,9 @@ dependencies { ...@@ -19,6 +19,9 @@ dependencies {
// runtimeOnly 'mysql:mysql-connector-java' // runtimeOnly 'mysql:mysql-connector-java'
// runtimeOnly 'org.postgresql:postgresql' // runtimeOnly 'org.postgresql:postgresql'
// sa-token
implementation "cn.dev33:sa-token-spring-boot-starter:${saTokenVersion}"
// swagger // swagger
implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}" implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}"
implementation "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}" implementation "org.hibernate.validator:hibernate-validator:${hibernateValidatorVersion}"
......
...@@ -5,11 +5,18 @@ import org.springframework.boot.SpringApplication; ...@@ -5,11 +5,18 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters; import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EntityScan(basePackageClasses = { Application.class, Jsr310JpaConverters.class }) @EnableJpaRepositories(basePackages = Application.BASE_PACKAGES)
@SpringBootApplication(scanBasePackages = "com.yiring") @EntityScan(
basePackageClasses = { Application.class, Jsr310JpaConverters.class },
basePackages = Application.BASE_PACKAGES
)
@SpringBootApplication(scanBasePackages = Application.BASE_PACKAGES)
public class Application { public class Application {
public static final String BASE_PACKAGES = "com.yiring";
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
} }
......
/* (C) 2021 YiRing, Inc. */ /* (C) 2021 YiRing, Inc. */
package com.yiring.app.config; package com.yiring.app.config;
import cn.dev33.satoken.exception.NotLoginException;
import com.yiring.common.core.Result; import com.yiring.common.core.Result;
import com.yiring.common.core.Status; import com.yiring.common.core.Status;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException; import javax.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.validation.BindException; import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
...@@ -67,13 +70,31 @@ public class GlobalExceptionHandler { ...@@ -67,13 +70,31 @@ public class GlobalExceptionHandler {
} }
/** /**
* 未登录异常(鉴权失败)
*
* @param e 异常信息
* @return 异常信息反馈 {@link Status#UNAUTHORIZED
*/
@ExceptionHandler(value = NotLoginException.class)
public Result<String> notLoginErrorHandler(Exception e) {
return Result.no(Status.UNAUTHORIZED);
}
/**
* 取消请求异常(忽略)
*/
@ExceptionHandler(value = ClientAbortException.class)
public void clientAbortExceptionHandler() {}
/**
* 其他异常 * 其他异常
* *
* @param e 异常信息 * @param e 异常信息
* @return 统一的500异常信息 {@link Status#INTERNAL_SERVER_ERROR * @return 统一的500异常信息 {@link Status#INTERNAL_SERVER_ERROR
*/ */
@ExceptionHandler(value = Exception.class) @ExceptionHandler(value = Exception.class)
public Result<String> defaultErrorHandler(Exception e) { public Result<String> defaultErrorHandler(Exception e, HttpServletResponse response) {
response.setStatus(Status.INTERNAL_SERVER_ERROR.value());
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
return Result.no(Status.INTERNAL_SERVER_ERROR, e); return Result.no(Status.INTERNAL_SERVER_ERROR, e);
} }
......
...@@ -18,13 +18,13 @@ import org.springframework.data.domain.Pageable; ...@@ -18,13 +18,13 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
/** /**
* 分页查询参数公共 * 分页查询参数公共类
* *
* @author ifzm * @author ifzm
* @version 0.1 2019/3/10 16:29 * @version 0.1 2019/3/10 16:29
*/ */
@SuppressWarnings({ "unused" }) @SuppressWarnings({ "unused" })
@ApiModel @ApiModel("分页查询参数公共类")
@Data @Data
@SuperBuilder @SuperBuilder
@NoArgsConstructor @NoArgsConstructor
......
/* (C) 2021 YiRing, Inc. */
package com.yiring.app.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
/**
* 公共数据响应
*
* @author ifzm
* @version 0.1
* 2022/3/23 16:47
*/
@ApiModel("公共数据响应")
@NoArgsConstructor
@Getter(value = AccessLevel.PRIVATE)
@Setter(value = AccessLevel.PRIVATE)
@FieldDefaults(level = AccessLevel.PRIVATE)
public class DataVo<T extends Serializable> implements Serializable {
private static final long serialVersionUID = 2472779197432240431L;
@ApiModelProperty(value = "数据")
T data;
/**
* 通常在带有时效性的数据查询时有用途(可选参数)
*/
@ApiModelProperty(value = "数据最新时间")
LocalDateTime latest;
/**
* 构建一个 DataVo
* @param data 数据
* @return DataVo
*/
@SuppressWarnings({ "unused" })
public static <R extends Serializable> DataVo<R> build(R data) {
return build(data, null);
}
/**
* 构建一个 DataVo
* @param data 数据
* @param latest 数据最新时间
* @return DataVo
*/
@SuppressWarnings({ "unused" })
public static <R extends Serializable> DataVo<R> build(R data, LocalDateTime latest) {
DataVo<R> vo = new DataVo<>();
vo.setData(data);
vo.setLatest(latest);
return vo;
}
}
...@@ -4,6 +4,7 @@ package com.yiring.app.vo; ...@@ -4,6 +4,7 @@ package com.yiring.app.vo;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
...@@ -33,6 +34,12 @@ public class PageVo<T extends Serializable> implements Serializable { ...@@ -33,6 +34,12 @@ public class PageVo<T extends Serializable> implements Serializable {
Long total; Long total;
/** /**
* 通常在带有时效性的数据查询时有用途(可选参数)
*/
@ApiModelProperty(value = "数据最新时间")
LocalDateTime latest;
/**
* 构建一个 PageVo * 构建一个 PageVo
* @param data 数据 * @param data 数据
* @param total 总数据量 * @param total 总数据量
...@@ -40,9 +47,22 @@ public class PageVo<T extends Serializable> implements Serializable { ...@@ -40,9 +47,22 @@ public class PageVo<T extends Serializable> implements Serializable {
*/ */
@SuppressWarnings({ "unused" }) @SuppressWarnings({ "unused" })
public static <R extends Serializable> PageVo<R> build(List<R> data, long total) { public static <R extends Serializable> PageVo<R> build(List<R> data, long total) {
return build(data, total, null);
}
/**
* 构建一个 PageVo
* @param data 数据
* @param total 总数据量
* @param latest 数据最新时间
* @return PageVo
*/
@SuppressWarnings({ "unused" })
public static <R extends Serializable> PageVo<R> build(List<R> data, long total, LocalDateTime latest) {
PageVo<R> vo = new PageVo<>(); PageVo<R> vo = new PageVo<>();
vo.setData(data); vo.setData(data);
vo.setTotal(total); vo.setTotal(total);
vo.setLatest(latest);
return vo; return vo;
} }
} }
...@@ -5,6 +5,7 @@ import com.yiring.app.param.IdParam; ...@@ -5,6 +5,7 @@ import com.yiring.app.param.IdParam;
import com.yiring.app.param.PageParam; import com.yiring.app.param.PageParam;
import com.yiring.app.vo.PageVo; import com.yiring.app.vo.PageVo;
import com.yiring.common.core.Result; import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -17,14 +18,14 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -17,14 +18,14 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@Validated @Validated
@Api(tags = "Hello World") @Api(tags = "Hello World")
@RestController("/") @RestController("/hello/")
public class HelloController { public class HelloController {
String text = "😎 Hello World"; String text = "😎 Hello World";
@GetMapping @GetMapping
public Result<String> hello() { public Result<String> hello() {
return Result.ok(text); return Result.no(Status.BAD_REQUEST, "测试错误");
} }
@GetMapping("id") @GetMapping("id")
......
...@@ -9,3 +9,13 @@ spring: ...@@ -9,3 +9,13 @@ spring:
open-in-view: true open-in-view: true
hibernate: hibernate:
ddl-auto: update ddl-auto: update
knife4j:
enable: true
basic:
enable: true
username: admin
password: 123456
setting:
enableOpenApi: false
enableDebug: true
...@@ -12,3 +12,13 @@ spring: ...@@ -12,3 +12,13 @@ spring:
h2: h2:
console: console:
enabled: true enabled: true
knife4j:
enable: true
basic:
enable: true
username: admin
password: 123456
setting:
enableOpenApi: false
enableDebug: true
server: server:
port: 8080 port: 8181
servlet: servlet:
context-path: /api context-path: /api
...@@ -7,7 +7,7 @@ spring: ...@@ -7,7 +7,7 @@ spring:
application: application:
name: "basic-api-app" name: "basic-api-app"
profiles: profiles:
include: auth,doc include: auth
active: mock active: mock
# DEBUG # DEBUG
......
/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.config;
import cn.dev33.satoken.interceptor.SaRouteInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 路由拦截器鉴权
*
* @author Jim
* @version 0.1
* 2022/1/28 20:35
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册Sa-Token的路由拦截器
registry
.addInterceptor(
new SaRouteInterceptor((req, res, handler) -> {
// 登录认证 -- 拦截所有路由,并排除 /auth/** 用于开放授权相关, 以及 swagger 相关
SaRouter
.match("/**")
.notMatchMethod(SaHttpMethod.OPTIONS.name())
// TODO: 实现用户权限相关后应移除下行代码
.notMatch("/**")
.notMatch("/auth/**")
.notMatch("/favicon.ico", "/**/*.html", "/**/*.js", "/**/*.css")
.notMatch("/v2/api-docs", "/v3/api-docs", "/swagger-resources/**")
.check(r -> StpUtil.checkLogin());
})
)
.addPathPatterns("/**");
}
}
/** /**
* @author Jim * @author Jim
* @version 0.1 * @version 0.1
* 2021/11/22 13:03 * 2022/3/23 16:18
*/ */
package com.yiring.common; package com.yiring.auth;
...@@ -6,4 +6,9 @@ dependencies { ...@@ -6,4 +6,9 @@ dependencies {
// swagger annotations // swagger annotations
implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}" implementation "io.swagger:swagger-annotations:${swaggerAnnotationsVersion}"
// hutool-extra
implementation "cn.hutool:hutool-extra:${hutoolVersion}"
// hutool-json
implementation "cn.hutool:hutool-json:${hutoolVersion}"
} }
/* (C) 2021 YiRing, Inc. */ /* (C) 2021 YiRing, Inc. */
package com.yiring.common.aspect; package com.yiring.common.aspect;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONObject;
import com.yiring.common.constant.DateFormatter; import com.yiring.common.constant.DateFormatter;
import com.yiring.common.core.Result; import com.yiring.common.core.Result;
import com.yiring.common.util.Commons; import com.yiring.common.util.Commons;
...@@ -11,12 +13,13 @@ import org.aspectj.lang.ProceedingJoinPoint; ...@@ -11,12 +13,13 @@ import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
/** /**
* 请求接口切面,记录接口耗时 * 请求接口切面,记录接口耗时相关
* *
* @author ifzm * @author ifzm
* @version 0.1 * @version 0.1
...@@ -27,6 +30,9 @@ import org.springframework.web.context.request.RequestContextHolder; ...@@ -27,6 +30,9 @@ import org.springframework.web.context.request.RequestContextHolder;
@Component @Component
public class RequestAspect { public class RequestAspect {
@Value("${debug}")
Boolean debug;
@Pointcut( @Pointcut(
"@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.ExceptionHandler)" "@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping) || @annotation(org.springframework.web.bind.annotation.ExceptionHandler)"
) )
...@@ -44,12 +50,20 @@ public class RequestAspect { ...@@ -44,12 +50,20 @@ public class RequestAspect {
String times = String.format("%.3fs", (double) (end - start) / 1000); String times = String.format("%.3fs", (double) (end - start) / 1000);
// Print Request Log (Optional Replace: MDC) // Print Request Log (Optional Replace: MDC)
String extra = "";
if (Boolean.TRUE.equals(debug)) {
extra += String.format("\nHeaders: %s", new JSONObject(ServletUtil.getHeaderMap(request)).toStringPretty());
extra += String.format("\nParams: %s", new JSONObject(ServletUtil.getParamMap(request)).toStringPretty());
if (result instanceof Result) {
extra += String.format("\nResponse Status: %s", ((Result<?>) result).getStatus());
}
}
log.info( log.info(
"[Request] {} - URL: {}, IP: {}, Times: {}", "[Request] URL: {}, IP: {}, Times: {}{}",
timestamp,
request.getRequestURL(), request.getRequestURL(),
Commons.getClientIpAddress(request), Commons.getClientIpAddress(request),
times times,
extra
); );
if (result instanceof Result) { if (result instanceof Result) {
......
/* (C) 2022 YiRing, Inc. */
package com.yiring.common.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.yiring.common.constant.DateFormatter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
/**
* DateTime Config
*
* @author ifzm
* 2020/7/8 00:35
*/
@Configuration
public class DateTimeConfig {
@Bean(name = "mapperObject")
public ObjectMapper getObjectMapper() {
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateFormatter.DATE_TIME));
timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateFormatter.DATE));
timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateFormatter.TIME));
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateFormatter.DATE_TIME));
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateFormatter.DATE));
timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateFormatter.TIME));
// feat: add AdminServerModule
// .setSerializationInclusion(JsonInclude.Include.NON_NULL)
return new ObjectMapper().registerModules(timeModule);
}
@Bean
public StringToLocalDateTimeConverter localDateTimeConverter() {
return source -> LocalDateTime.parse(source, DateFormatter.DATE_TIME);
}
@Bean
public StringToLocalDateConverter localDateConverter() {
return source -> LocalDate.parse(source, DateFormatter.DATE);
}
@Bean
public StringToLocalTimeConverter localTimeConverter() {
return source -> LocalTime.parse(source, DateFormatter.TIME);
}
interface StringToLocalDateTimeConverter extends Converter<String, LocalDateTime> {}
interface StringToLocalDateConverter extends Converter<String, LocalDate> {}
interface StringToLocalTimeConverter extends Converter<String, LocalTime> {}
}
/* (C) 2022 YiRing, Inc. */ /* (C) 2022 YiRing, Inc. */
package com.yiring.app.config; package com.yiring.common.config;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
......
...@@ -49,6 +49,12 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -49,6 +49,12 @@ public class Result<T extends Serializable> implements Serializable {
Integer status; Integer status;
/** /**
* 业务标识码
*/
@ApiModelProperty(value = "业务标识码", example = "0")
Integer code;
/**
* 响应消息 * 响应消息
*/ */
@ApiModelProperty(value = "消息", example = "OK") @ApiModelProperty(value = "消息", example = "OK")
...@@ -104,7 +110,7 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -104,7 +110,7 @@ public class Result<T extends Serializable> implements Serializable {
* @see Status#BAD_REQUEST * @see Status#BAD_REQUEST
*/ */
public static <T extends Serializable> Result<T> no(Status status) { public static <T extends Serializable> Result<T> no(Status status) {
return no(status, null, null); return no(status, null, null, null);
} }
/** /**
...@@ -114,7 +120,7 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -114,7 +120,7 @@ public class Result<T extends Serializable> implements Serializable {
* @see Status * @see Status
*/ */
public static <T extends Serializable> Result<T> no(Status status, String details) { public static <T extends Serializable> Result<T> no(Status status, String details) {
return no(status, details, null); return no(status, null, details, null);
} }
/** /**
...@@ -124,7 +130,7 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -124,7 +130,7 @@ public class Result<T extends Serializable> implements Serializable {
* @see Status * @see Status
*/ */
public static <T extends Serializable> Result<T> no(Status status, Throwable error) { public static <T extends Serializable> Result<T> no(Status status, Throwable error) {
return no(status, null, error); return no(status, null, null, error);
} }
/** /**
...@@ -133,11 +139,12 @@ public class Result<T extends Serializable> implements Serializable { ...@@ -133,11 +139,12 @@ public class Result<T extends Serializable> implements Serializable {
* @return Result * @return Result
* @see Status * @see Status
*/ */
public static <T extends Serializable> Result<T> no(Status status, String details, Throwable error) { public static <T extends Serializable> Result<T> no(Status status, Integer code, String details, Throwable error) {
Result<T> result = (Result<T>) Result Result<T> result = (Result<T>) Result
.builder() .builder()
.status(status.value()) .status(status.value())
.message(status.getReasonPhrase()) .message(status.getReasonPhrase())
.code(code)
.details(details) .details(details)
.build(); .build();
......
knife4j:
enable: true
basic:
enable: true
username: admin
password: 123456
setting:
enableOpenApi: false
enableDebug: true
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation "org.apache.commons:commons-lang3:${commonsLangVersion}"
} }
...@@ -11,7 +11,6 @@ import java.io.IOException; ...@@ -11,7 +11,6 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
/** /**
* Mapping 序列化 * Mapping 序列化
...@@ -40,7 +39,7 @@ public class ObjectMappingSerializer extends StdSerializer<Object> implements Co ...@@ -40,7 +39,7 @@ public class ObjectMappingSerializer extends StdSerializer<Object> implements Co
String mapping = mappingSerialize.mapping(); String mapping = mappingSerialize.mapping();
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
if (!StringUtils.isEmpty(mapping)) { if (mapping.length() > 0) {
String[] strings = mapping.split(","); String[] strings = mapping.split(",");
for (String str : strings) { for (String str : strings) {
String[] split = str.split(":"); String[] split = str.split(":");
...@@ -56,7 +55,7 @@ public class ObjectMappingSerializer extends StdSerializer<Object> implements Co ...@@ -56,7 +55,7 @@ public class ObjectMappingSerializer extends StdSerializer<Object> implements Co
@Override @Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
String text = mapping.get(Objects.toString(value)); String text = mapping.get(Objects.toString(value));
if (StringUtils.isEmpty(text)) { if (text == null || text.length() == 0) {
gen.writeObject(value); gen.writeObject(value);
} else { } else {
gen.writeString(text); gen.writeString(text);
......
...@@ -13,18 +13,24 @@ buildscript { ...@@ -13,18 +13,24 @@ buildscript {
saTokenVersion = '1.28.0' saTokenVersion = '1.28.0'
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 // https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
commonsLangVersion = '3.12.0' commonsLangVersion = '3.12.0'
// https://mvnrepository.com/artifact/cn.hutool/hutool-all
hutoolVersion = '5.7.22'
// https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core
xxlJobVersion = '2.3.0'
// https://mvnrepository.com/artifact/com.alibaba/fastjson
fastJsonVersion = '1.2.79'
} }
} }
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '2.5.8' id 'org.springframework.boot' version '2.5.10'
// https://plugins.gradle.org/plugin/io.spring.dependency-management // https://plugins.gradle.org/plugin/io.spring.dependency-management
id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'io.spring.dependency-management' version '1.0.11.RELEASE'
// https://plugins.gradle.org/plugin/com.diffplug.spotless // https://plugins.gradle.org/plugin/com.diffplug.spotless
id "com.diffplug.spotless" version "6.0.0" id "com.diffplug.spotless" version "6.3.0"
// https://plugins.gradle.org/plugin/com.github.spotbugs // https://plugins.gradle.org/plugin/com.github.spotbugs
id "com.github.spotbugs" version "4.7.10" // id "com.github.spotbugs" version "4.7.10"
} }
group = 'com.yiring' group = 'com.yiring'
...@@ -46,7 +52,7 @@ subprojects { ...@@ -46,7 +52,7 @@ subprojects {
apply plugin: 'io.spring.dependency-management' apply plugin: 'io.spring.dependency-management'
apply plugin: 'java' apply plugin: 'java'
apply plugin: "com.diffplug.spotless" apply plugin: "com.diffplug.spotless"
apply plugin: "com.github.spotbugs" // apply plugin: "com.github.spotbugs"
configurations { configurations {
compileOnly { compileOnly {
...@@ -98,7 +104,7 @@ subprojects { ...@@ -98,7 +104,7 @@ subprojects {
licenseHeader '/* (C) $YEAR YiRing, Inc. */' licenseHeader '/* (C) $YEAR YiRing, Inc. */'
// https://www.npmjs.com/package/prettier // https://www.npmjs.com/package/prettier
// https://www.npmjs.com/package/prettier-plugin-java // https://www.npmjs.com/package/prettier-plugin-java
prettier(['prettier': '2.5.1', 'prettier-plugin-java': '1.6.0']).config([ prettier(['prettier': '2.6.0', 'prettier-plugin-java': '1.6.1']).config([
'parser' : 'java', 'parser' : 'java',
'tabWidth' : 4, 'tabWidth' : 4,
'printWidth': 120, 'printWidth': 120,
...@@ -116,7 +122,7 @@ def hook = new File("$rootProject.projectDir/.git/hooks/pre-commit") ...@@ -116,7 +122,7 @@ def hook = new File("$rootProject.projectDir/.git/hooks/pre-commit")
hook.text = """#!/bin/bash hook.text = """#!/bin/bash
#set -x #set -x
./gradlew spotlessCheck spotbugsMain ./gradlew spotlessCheck
RESULT=\$? RESULT=\$?
exit \$RESULT exit \$RESULT
......
### IDE 插件推荐清单
> IDEA
<!-- prettier-ignore -->
-[IDE Eval Rest](https://www.cnblogs.com/wang-cong/p/15150585.html) - IDEA 无限重置试用插件
-[.ignore](https://plugins.jetbrains.com/plugin/7495--ignore)
-[GitToolBox](https://plugins.jetbrains.com/plugin/7499-gittoolbox)
-[Grep Console](https://plugins.jetbrains.com/plugin/7125-grep-console)
-[Rainbow Brackets](https://plugins.jetbrains.com/plugin/10080-rainbow-brackets)
-[Spotless Gradle](https://plugins.jetbrains.com/plugin/18321-spotless-gradle)
- [Prettier](https://plugins.jetbrains.com/plugin/10456-prettier)
- [Nyan Progress Bar](https://plugins.jetbrains.com/plugin/8575-nyan-progress-bar)
- [JPA Buddy](https://plugins.jetbrains.com/plugin/15075-jpa-buddy)
## 技术栈
### 基础
<!-- prettier-ignore -->
- [Vue v3.x](https://v3.vuejs.org/guide/introduction.html)
- [uni-app@vue3 v3.x-alpha](https://uniapp.dcloud.io/tutorial/migration-to-vue3.html), 依赖 `HBuilderX Alpha` 版本编辑器
- [TypeScript](https://www.typescriptlang.org/zh/)
- [Sass](https://www.sass.hk/)
### UI
<!-- prettier-ignore -->
- [uni-ui@vue3](https://github.com/dcloudio/uni-ui)
- 备选方案: [uView](https://uviewui.com/), 由于尚未完成 Vue3 的适配, 暂不推荐使用
### Tools
<!-- prettier-ignore -->
- [Lodash](https://github.com/lodash/lodash) 函数库
- [Dayjs](https://github.com/iamkun/dayjs) 时间库
- [@vueuse/core](https://github.com/vueuse/vueuse) Vue2/3 实用工具集
- [mock.js](https://github.com/nuysoft/Mock) 数据模拟
### 网络
<!-- prettier-ignore -->
- [uni.request](https://uniapp.dcloud.net.cn/api/request/request.html)
- [uni-ajax](https://uniajax.ponjs.com/)
### 图表
<!-- prettier-ignore -->
- [ucharts](https://gitee.com/uCharts/uCharts)
### 构建工具
<!-- prettier-ignore -->
- [HBuilderX Alpha](https://download1.dcloud.net.cn/hbuilderx/changelog/3.4.2.20220310-alpha.html)
- [Vite v2.x](https://vitejs.dev)
### 其他插件
<!-- prettier-ignore -->
- [prettier](https://prettier.io) 代码格式化工具
- [husky](https://github.com/typicode/husky) Git hooks 插件
- [ESLint](https://eslint.org) 代码规范检查工具
- [stylelint](https://stylelint.io) CSS 规范检查工具
- [commitizen](https://github.com/commitizen) Git 提交规范工具
- [lint-staged](https://github.com/okonet/lint-staged)
- ...
# 开发工作流规范
> 🥇 适用于前后端项目开发
## 开发前必读 🔥
以下内容,项目负责人及开发团队成员务必遵守,共同营造一个良好的开发环境
1. 使用代码规范检查工具(前端:`ESLint`,后端:`Spotless`),规范项通常使用业内标准,再由团队协商调整部分检查项
2. 使用代码格式规范工具统一编码风格(前端:`Prettier`,后端:`Spotless + Prettier for Java`
3. 使用 `GitHook` 来自动化规范提交前的代码初步检查
4. ~~TODO...~~
## 代码管理规范 ⚠️
重要:所有项目统一由负责人在 GitLab 上创建群组,与项目有关的前后端工程,全部在群组内预先创建好,并分配人员,不允许将项目挂在私人账号下进行协同开发。
## Git 规范 ✅
简化使用 [git-flow](https://www.gitflow.com/),具体说明参考如下
### 分支规范
1. master (主分支)
2. develop (开发分支)
3. dev_fzm (开发人员分支,fzm 代表用户方治民)
4. release、hotfix (视情况产生的分支,用完删除)
共包含五种类型的分支,暂定工作流程图如下
![image](http://img.ifzm.cn/git-flow.2020-02-14.png)
develop 分支允许开发时用于代码合并,保持最新的汇总版本
develop 分支之上为权限分支,仅由负责人负责合并代码或创建新分支,以及发布版本
### 提交规范
> **推荐方式**: 使用 [cz-cli](https://github.com/commitizen/cz-cli) 来提交代码,推荐使用 `cz-conventional-changelog` 插件来提交更新日志
<!-- prettier-ignore -->
- 禁止提交自己的本地配置,如:`.idea/.vscode``local/env` 配置(特殊情况下需要团队保持强一致时可以提交)
- 禁止提交不能通过编译的代码
- 养成早提交、多提交习惯,最好每开发一个功能都 `commit` 一次,提交前需要先更新
### Commit Message 规范
规范 `Commit Message` 格式(注意冒号后面有空格)
```md
<type>: <subject> <#id>
```
#### 常用的 type 类别
<!-- prettier-ignore -->
- `feat`:新增 xxx 功能
- `fix`:修复 xxx Bug
- `docs`:变更 xxx 文档
- `style`: 变更 xxx 代码格式或注释
- `refactor`:重构 xxx 功能或方法
- `test`:调试 xxx 功能或新增 xxx 测试用例
- `chore`:构建过程或辅助工具的变动(较少出现,项目构建之初基本已确定好)
- `build`: 发布 v0.0.1 版本
#### subject 简述
`subject``commit` 描述信息,尽可能简明扼要。
#### id 标识
`#id` 代表 [PingCode](https://yiring.pingcode.com) 任务、Bug 编号(可选,如果有对应的必须填写)
#### 示例
```sh
git commit -m 'feat: 新增用户登录功能'
git commit -m 'fix: 修复由 token 问题引起的需要重复登录 Bug #1002'
```
version: '3'
services:
# 应用服务
basic-api:
build:
context: .
dockerfile: Dockerfile
# 镜像名称
image: 127.0.0.1:18500/basic/basic-api:0.0.1
# 容器名称
container_name: 'basic-api'
# 端口绑定
ports:
- "18181:8181"
# 当 docker 重启时,容器自动启动
restart: always
# 挂载目录
volumes:
- "/volume/basic/app/data:/data"
- "/volume/basic/app/logs:/logs"
environment:
# 设置时区
- TZ=Asia/Shanghai
# 指定网络
networks:
- basic
networks:
basic:
{
"name": "basic-api-project",
"version": "0.0.1",
"devDependencies": {
"cz-conventional-changelog": "^3.3.0"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论