diff --git a/README.md b/README.md
index 72febe9..5d22547 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
## 子项目
- `pro-common`: 通用代码
-- `pro-mbg`: Mybatis Generator
+- `pro-mbg`: Mybatis Generator,用于自动生成 MyBatis 数据库模型与字段映射
- `pro-admin`: 使用 JWT 保持登陆状态的后台管理应用,包括基于角色的访问控制(RBAC),示例前端项目 [sbs-admin-web](https://github.com/deepraining/sbs-admin-web)
- `pro-front`: 使用 Session-Cookie 保持登陆状态的前端应用
- `pro-search`: 使用 [ElasticSearch](https://www.elastic.co/) 来做文本搜索引擎
@@ -26,6 +26,8 @@
- `pro-rws`: 数据库读写分离示例项目
- `pro-mdb`: 多数据库、跨库读写示例项目
- `pro-amqp`: 异步消息队列(`RabbitMQ`)示例项目
+- `pro-proto`: 使用 [protobuf](https://github.com/protocolbuffers/protobuf) 作为API数据交互格式,替代常用的 `json`
+- `pro-protogen`: 从 MySql 数据库中的生成表对应的 `proto` 文件
## 扩展 Gradle Tasks
diff --git a/build.gradle b/build.gradle
index 4677d6c..98c149a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,6 +16,7 @@ import java.nio.file.Paths;
plugins {
id 'java'
id 'application'
+ id 'idea'
id 'checkstyle'
id 'com.github.sherter.google-java-format' version '0.8'
id 'org.springframework.boot' version '2.1.4.RELEASE'
@@ -38,6 +39,7 @@ allprojects {
subprojects {
apply plugin: 'java'
apply plugin: 'application'
+ apply plugin: 'idea'
apply plugin: 'io.freefair.lombok'
dependencies {
diff --git a/pro-proto/build.gradle b/pro-proto/build.gradle
new file mode 100644
index 0000000..055201f
--- /dev/null
+++ b/pro-proto/build.gradle
@@ -0,0 +1,46 @@
+buildscript {
+ repositories {
+ mavenLocal()
+ // https://maven.aliyun.com/mvn/view
+ maven { url 'https://maven.aliyun.com/repository/public' }
+ jcenter()
+ mavenCentral()
+ maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
+ maven { url 'https://plugins.gradle.org/m2/' }
+ }
+ dependencies {
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.17'
+ }
+}
+
+plugins {
+ id 'org.springframework.boot'
+ id "io.spring.dependency-management"
+}
+
+group = 'dr.sbs'
+// version should not have '-'
+version = new Date().format("yyyy.MMdd.HHmm", TimeZone.getTimeZone("GMT+08:00"))
+archivesBaseName = 'sbs-proto'
+sourceCompatibility = 1.8
+description = 'Spring Boot Starter Protobuf Application.'
+
+mainClassName = 'dr.sbs.proto.App'
+
+apply plugin: 'com.google.protobuf'
+
+dependencies {
+ implementation 'org.springframework.boot:spring-boot-starter-security:2.1.4.RELEASE'
+ implementation 'org.springframework.session:spring-session-data-redis:2.1.4.RELEASE'
+ implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.1.4.RELEASE'
+
+ implementation 'net.logstash.logback:logstash-logback-encoder:6.0'
+
+ implementation 'com.google.protobuf:protobuf-java:3.19.1'
+ implementation 'com.google.protobuf:protobuf-java-util:3.19.1'
+
+ // projects
+ implementation project(':pro-mbg')
+ implementation project(':pro-common')
+ implementation project(':pro-protogen')
+}
diff --git a/pro-proto/lombok.config b/pro-proto/lombok.config
new file mode 100644
index 0000000..6aa51d7
--- /dev/null
+++ b/pro-proto/lombok.config
@@ -0,0 +1,2 @@
+# This file is generated by the 'io.freefair.lombok' Gradle plugin
+config.stopBubbling = true
diff --git a/pro-proto/src/main/java/dr/sbs/proto/App.java b/pro-proto/src/main/java/dr/sbs/proto/App.java
new file mode 100644
index 0000000..1d181e2
--- /dev/null
+++ b/pro-proto/src/main/java/dr/sbs/proto/App.java
@@ -0,0 +1,31 @@
+package dr.sbs.proto;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.ApplicationContext;
+import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
+
+@Slf4j
+@SpringBootApplication
+@EnableRedisHttpSession
+public class App extends SpringBootServletInitializer {
+
+ public static void main(String[] args) {
+ // Split config files into outer of jar file
+ System.setProperty("spring.config.additional-location", "file:${HOME}/.sbs-proto/");
+
+ ApplicationContext context = SpringApplication.run(App.class, args);
+ String serverPort = context.getEnvironment().getProperty("server.port");
+ log.info("App started at http://127.0.0.1:" + serverPort);
+ }
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ // Split config files into outer of jar file
+ System.setProperty("spring.config.additional-location", "file:${HOME}/.sbs-proto/");
+ return application.sources(App.class);
+ }
+}
diff --git a/pro-proto/src/main/java/dr/sbs/proto/Init.java b/pro-proto/src/main/java/dr/sbs/proto/Init.java
new file mode 100644
index 0000000..8f02121
--- /dev/null
+++ b/pro-proto/src/main/java/dr/sbs/proto/Init.java
@@ -0,0 +1,22 @@
+package dr.sbs.proto;
+
+import dr.sbs.common.util.UuidUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Init implements ApplicationRunner {
+ @Value("${app.workerId}")
+ private long workerId;
+
+ @Value("${app.dataCenterId}")
+ private long dataCenterId;
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ // Init UuidUtil
+ UuidUtil.init(workerId, dataCenterId);
+ }
+}
diff --git a/pro-proto/src/main/java/dr/sbs/proto/aspect/BindingResultAspect.java b/pro-proto/src/main/java/dr/sbs/proto/aspect/BindingResultAspect.java
new file mode 100644
index 0000000..14228ed
--- /dev/null
+++ b/pro-proto/src/main/java/dr/sbs/proto/aspect/BindingResultAspect.java
@@ -0,0 +1,45 @@
+package dr.sbs.proto.aspect;
+
+import dr.sbs.common.CommonResult;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+
+/**
+ * global error handling aspect
+ *
+ *
note: `controller`的方法里,用`@Validated`标记需要验证的参数,最后面要有`BindingResult bindingResult`参数,才能生效
+ */
+@Aspect
+@Component
+@Order(2)
+public class BindingResultAspect {
+ @Pointcut("execution(public * dr.sbs.proto.controller.*.*(..))")
+ public void bindingResult() {
+ //
+ }
+
+ @Around("bindingResult()")
+ public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
+ Object[] args = joinPoint.getArgs();
+ for (Object arg : args) {
+ if (arg instanceof BindingResult) {
+ BindingResult result = (BindingResult) arg;
+ if (result.hasErrors()) {
+ FieldError fieldError = result.getFieldError();
+ if (fieldError != null) {
+ return CommonResult.validateFailed(fieldError.getDefaultMessage());
+ } else {
+ return CommonResult.validateFailed();
+ }
+ }
+ }
+ }
+ return joinPoint.proceed();
+ }
+}
diff --git a/pro-proto/src/main/java/dr/sbs/proto/aspect/WebLogAspect.java b/pro-proto/src/main/java/dr/sbs/proto/aspect/WebLogAspect.java
new file mode 100644
index 0000000..5581bf7
--- /dev/null
+++ b/pro-proto/src/main/java/dr/sbs/proto/aspect/WebLogAspect.java
@@ -0,0 +1,126 @@
+package dr.sbs.proto.aspect;
+
+import dr.sbs.common.bo.WebLog;
+import dr.sbs.common.util.JsonUtil;
+import dr.sbs.common.util.RequestUtil;
+import io.swagger.annotations.ApiOperation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import net.logstash.logback.marker.Markers;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.multipart.MultipartFile;
+
+/** global log handling aspect */
+@Aspect
+@Component
+@Order(1)
+@Slf4j
+public class WebLogAspect {
+ private ThreadLocal startTime = new ThreadLocal<>();
+
+ @Pointcut("execution(public * dr.sbs.proto.controller.*.*(..))")
+ public void webLog() {
+ //
+ }
+
+ @Before("webLog()")
+ public void doBefore(JoinPoint joinPoint) throws Throwable {
+ startTime.set(System.currentTimeMillis());
+ }
+
+ @AfterReturning(value = "webLog()", returning = "ret")
+ public void doAfterReturning(Object ret) throws Throwable {
+ //
+ }
+
+ @Around("webLog()")
+ public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
+ // Get current request object
+ ServletRequestAttributes attributes =
+ (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = attributes.getRequest();
+
+ // Record request information
+ WebLog webLog = new WebLog();
+ Object result = joinPoint.proceed();
+ Signature signature = joinPoint.getSignature();
+ MethodSignature methodSignature = (MethodSignature) signature;
+ Method method = methodSignature.getMethod();
+ if (method.isAnnotationPresent(ApiOperation.class)) {
+ ApiOperation log = method.getAnnotation(ApiOperation.class);
+ webLog.setDescription(log.value());
+ }
+ long endTime = System.currentTimeMillis();
+ webLog.setBasePath(RequestUtil.getBasePath(request));
+ webLog.setIp(request.getRemoteUser());
+ webLog.setMethod(request.getMethod());
+ webLog.setParameter(getParameter(method, joinPoint.getArgs()));
+ webLog.setResult(result);
+ webLog.setSpendTime((int) (endTime - startTime.get()));
+ webLog.setStartTime(startTime.get());
+ webLog.setUri(request.getRequestURI());
+ webLog.setUrl(request.getRequestURL().toString());
+ Map logMap = new HashMap<>();
+ logMap.put("url", webLog.getUrl());
+ logMap.put("method", webLog.getMethod());
+ logMap.put("parameter", webLog.getParameter());
+ logMap.put("spendTime", webLog.getSpendTime());
+ logMap.put("description", webLog.getDescription());
+ log.info(Markers.appendEntries(logMap), JsonUtil.objectToJson(webLog));
+ return result;
+ }
+
+ /** Get parameters by HTTP method and input arguments */
+ private Object getParameter(Method method, Object[] args) {
+ List