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 argList = new ArrayList<>(); + Parameter[] parameters = method.getParameters(); + for (int i = 0; i < parameters.length; i++) { + RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); + if (requestBody != null) { + argList.add(args[i]); + } + RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); + if (requestParam != null) { + Map map = new HashMap<>(); + String key = parameters[i].getName(); + if (!StringUtils.isEmpty(requestParam.value())) { + key = requestParam.value(); + } + if (args[i] instanceof MultipartFile) { + map.put(key, ((MultipartFile) args[i]).getOriginalFilename()); + } else { + map.put(key, args[i]); + } + argList.add(map); + } + } + if (argList.size() == 0) { + return null; + } else if (argList.size() == 1) { + return argList.get(0); + } else { + return argList; + } + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/bo/UserInfo.java b/pro-proto/src/main/java/dr/sbs/proto/bo/UserInfo.java new file mode 100644 index 0000000..b9a64ca --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/bo/UserInfo.java @@ -0,0 +1,56 @@ +package dr.sbs.proto.bo; + +import dr.sbs.mbg.model.FrontUser; +import java.util.Arrays; +import java.util.Collection; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +public class UserInfo implements UserDetails { + private FrontUser user; + + public UserInfo(FrontUser user) { + this.user = user; + } + + @Override + public Collection getAuthorities() { + // get current user's privileges + return Arrays.asList(new SimpleGrantedAuthority("NONE")); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getUsername(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public FrontUser getUser() { + return user; + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/component/GlobalExceptionHandler.java b/pro-proto/src/main/java/dr/sbs/proto/component/GlobalExceptionHandler.java new file mode 100644 index 0000000..3721d83 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/component/GlobalExceptionHandler.java @@ -0,0 +1,22 @@ +package dr.sbs.proto.component; + +import dr.sbs.common.CommonResult; +import dr.sbs.common.exception.ApiException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +// 全局异常处理 +// 注:把这个文件复制到每项目的主包名下,才能生效 +// 或者启动时设置 `@SpringBootApplication(scanBasePackages = {"dr.sbs"})` +@ControllerAdvice +public class GlobalExceptionHandler { + @ResponseBody + @ExceptionHandler(value = ApiException.class) + public CommonResult handle(ApiException e) { + if (e.getErrorCode() != null) { + return CommonResult.failed(e.getErrorCode()); + } + return CommonResult.failed(e.getMessage()); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/component/GoAccessDeniedHandler.java b/pro-proto/src/main/java/dr/sbs/proto/component/GoAccessDeniedHandler.java new file mode 100644 index 0000000..07dbd9b --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/component/GoAccessDeniedHandler.java @@ -0,0 +1,35 @@ +package dr.sbs.proto.component; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +public class GoAccessDeniedHandler implements AccessDeniedHandler { + @Override + public void handle( + HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException) + throws IOException, ServletException { + if (request.getServletPath().startsWith("/api")) { + // api, return a json result + + response.setHeader("Content-Type", "application/json;charset=utf-8"); + response + .getWriter() + .print( + "{\"code\":4,\"message\":\"" + + "Not authorized: " + + accessDeniedException.getMessage() + + "\"}"); + response.getWriter().flush(); + } else { + // route page, redirect to login page + + response.sendRedirect("account/login"); + } + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationEntryPoint.java b/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationEntryPoint.java new file mode 100644 index 0000000..28b0cb6 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationEntryPoint.java @@ -0,0 +1,35 @@ +package dr.sbs.proto.component; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +public class GoAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) + throws IOException, ServletException { + if (request.getServletPath().startsWith("/api")) { + // api, return a json result + + response.setHeader("Content-Type", "application/json;charset=utf-8"); + response + .getWriter() + .print( + "{\"code\":3,\"message\":\"" + + "No privileges: " + + authException.getMessage() + + "\"}"); + response.getWriter().flush(); + } else { + // route page, redirect to login page + + response.sendRedirect("account/login"); + } + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationFailureHandler.java b/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationFailureHandler.java new file mode 100644 index 0000000..4621699 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationFailureHandler.java @@ -0,0 +1,21 @@ +package dr.sbs.proto.component; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +public class GoAuthenticationFailureHandler implements AuthenticationFailureHandler { + @Override + public void onAuthenticationFailure( + HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) + throws IOException, ServletException { + response.setHeader("Content-Type", "application/json;charset=utf-8"); + response + .getWriter() + .print("{\"code\":1,\"message\":\"" + "Login Failed: " + exception.getMessage() + "\"}"); + response.getWriter().flush(); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationSuccessHandler.java b/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationSuccessHandler.java new file mode 100644 index 0000000..53b6f1d --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/component/GoAuthenticationSuccessHandler.java @@ -0,0 +1,19 @@ +package dr.sbs.proto.component; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +public class GoAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + response.setHeader("Content-Type", "application/json;charset=utf-8"); + response.getWriter().print("{\"code\":0,\"message\":\"Login succeeded\"}"); + response.getWriter().flush(); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/component/GoLogoutSuccessHandler.java b/pro-proto/src/main/java/dr/sbs/proto/component/GoLogoutSuccessHandler.java new file mode 100644 index 0000000..67f36d9 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/component/GoLogoutSuccessHandler.java @@ -0,0 +1,19 @@ +package dr.sbs.proto.component; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +public class GoLogoutSuccessHandler implements LogoutSuccessHandler { + @Override + public void onLogoutSuccess( + HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + response.setHeader("Content-Type", "application/json;charset=utf-8"); + response.getWriter().print("{\"code\":0,\"message\":\"Logout succeeded\"}"); + response.getWriter().flush(); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/component/SpringUtils.java b/pro-proto/src/main/java/dr/sbs/proto/component/SpringUtils.java new file mode 100644 index 0000000..ddfe817 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/component/SpringUtils.java @@ -0,0 +1,96 @@ +package dr.sbs.proto.component; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class SpringUtils implements BeanFactoryPostProcessor { + private static ConfigurableListableBeanFactory beanFactory; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + SpringUtils.beanFactory = beanFactory; + } + + public static ConfigurableListableBeanFactory getBeanFactory() { + return beanFactory; + } + + /** + * 获取对象 + * + * @param name bean名称 + * @return Object 一个以所给名字注册的bean的实例 + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException { + if (getBeanFactory() == null) { + System.out.println("本地调试Main模式,没有BeanFactory,忽略错误"); + return null; + } else { + T result = (T) getBeanFactory().getBean(name); + return result; + } + } + + /** + * 获取类型为requiredType的对象 + * + * @param name bean名称 + * @return bean + */ + public static T getBean(Class name) throws BeansException { + if (getBeanFactory() == null) { + System.out.println("本地调试Main模式,没有BeanFactory,忽略错误"); + return null; + } else { + T result = (T) getBeanFactory().getBean(name); + return result; + } + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name bean名称 + * @return boolean + */ + public static boolean containsBean(String name) { + return getBeanFactory().containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 + * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name bean名称 + * @return boolean + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().isSingleton(name); + } + + /** + * @param name bean名称 + * @return Class 注册对象的类型 + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name bean名称 + * @return list + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { + return getBeanFactory().getAliases(name); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/config/JacksonConfig.java b/pro-proto/src/main/java/dr/sbs/proto/config/JacksonConfig.java new file mode 100644 index 0000000..f243ee7 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/config/JacksonConfig.java @@ -0,0 +1,29 @@ +package dr.sbs.proto.config; + +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import java.math.BigInteger; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; + +@Configuration +public class JacksonConfig { + + /** + * Transform long to string when jackson serialize. + * + * @return Jackson2ObjectMapperBuilderCustomizer + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + + return new Jackson2ObjectMapperBuilderCustomizer() { + @Override + public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) { + jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance); + jacksonObjectMapperBuilder.serializerByType(BigInteger.class, ToStringSerializer.instance); + } + }; + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/config/MyBatisConfig.java b/pro-proto/src/main/java/dr/sbs/proto/config/MyBatisConfig.java new file mode 100644 index 0000000..a476288 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/config/MyBatisConfig.java @@ -0,0 +1,12 @@ +package dr.sbs.proto.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +@MapperScan({"dr.sbs.mbg.mapper", "dr.sbs.proto.dao"}) +public class MyBatisConfig { + // +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/config/SecurityConfig.java b/pro-proto/src/main/java/dr/sbs/proto/config/SecurityConfig.java new file mode 100644 index 0000000..6eab298 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/config/SecurityConfig.java @@ -0,0 +1,86 @@ +package dr.sbs.proto.config; + +import dr.sbs.mbg.model.FrontUser; +import dr.sbs.proto.bo.UserInfo; +import dr.sbs.proto.component.GoAccessDeniedHandler; +import dr.sbs.proto.component.GoAuthenticationEntryPoint; +import dr.sbs.proto.component.GoAuthenticationFailureHandler; +import dr.sbs.proto.component.GoAuthenticationSuccessHandler; +import dr.sbs.proto.component.GoLogoutSuccessHandler; +import dr.sbs.proto.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Autowired private UserService userService; + + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception { + httpSecurity + .authorizeRequests() + .antMatchers("/user", "/api/user/**") + .authenticated() + .anyRequest() + .permitAll() + // Every cross origin request will make a OPTIONS request before its real request + .antMatchers(HttpMethod.OPTIONS) + .permitAll() + .and() + .exceptionHandling() + .accessDeniedHandler(new GoAccessDeniedHandler()) + .authenticationEntryPoint(new GoAuthenticationEntryPoint()) + .and() + .formLogin() + .loginPage("/account/login") + .loginProcessingUrl("/api/account/login") + .successHandler(new GoAuthenticationSuccessHandler()) + .failureHandler(new GoAuthenticationFailureHandler()) + .and() + .logout() + .logoutUrl("/api/account/logout") + .logoutSuccessHandler(new GoLogoutSuccessHandler()) + .invalidateHttpSession(true) + .deleteCookies("JSESSIONID") + .and() + .csrf() + .disable(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder()); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public UserDetailsService userDetailsService() { + // Get logged-in user information + return new UserDetailsService() { + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + FrontUser user = userService.getByUsername(username); + if (user != null) { + return new UserInfo(user); + } + throw new UsernameNotFoundException("Username or password is not correct"); + } + }; + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/config/Swagger2Config.java b/pro-proto/src/main/java/dr/sbs/proto/config/Swagger2Config.java new file mode 100644 index 0000000..cb6afcd --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/config/Swagger2Config.java @@ -0,0 +1,34 @@ +package dr.sbs.proto.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +@Profile({"dev"}) +public class Swagger2Config { + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo()) + .select() + .apis(RequestHandlerSelectors.basePackage("dr.sbs.proto.controller")) + .paths(PathSelectors.any()) + .build(); + } + + private ApiInfo apiInfo() { + return new ApiInfoBuilder() + .title("Spring Boot Starter Protobuf Application") + .version("1.0") + .build(); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/controller/AccountController.java b/pro-proto/src/main/java/dr/sbs/proto/controller/AccountController.java new file mode 100644 index 0000000..5dc9f90 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/controller/AccountController.java @@ -0,0 +1,83 @@ +package dr.sbs.proto.controller; + +import dr.sbs.common.CommonResult; +import dr.sbs.mbg.model.FrontUser; +import dr.sbs.proto.all.AllProto; +import dr.sbs.proto.dto.UpdatePasswordParam; +import dr.sbs.proto.dto.UserCreateParam; +import dr.sbs.proto.service.UserService; +import java.io.IOException; +import java.io.InputStream; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("/api/account") +public class AccountController { + @Autowired private UserService userService; + + public CommonResult register(UserCreateParam userCreateParam) { + FrontUser user = userService.register(userCreateParam); + if (user != null) return CommonResult.success(user); + return CommonResult.failed(); + } + + @PostMapping( + value = "/register", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity registerPB(InputStream reqStream) throws IOException { + AllProto.UserCreateParamProto userCreateParamProto = + AllProto.UserCreateParamProto.parseFrom(reqStream); + UserCreateParam userCreateParam = new UserCreateParam(); + userCreateParam.setUsername(userCreateParamProto.getUsername()); + userCreateParam.setEmail(userCreateParamProto.getEmail()); + userCreateParam.setPassword(userCreateParamProto.getPassword()); + + CommonResult result = register(userCreateParam); + + return ProtoUtil.responseFrontUser(result); + } + + public CommonResult updatePassword(UpdatePasswordParam updatePasswordParam) { + int count = userService.updatePassword(updatePasswordParam); + if (count > 0) return CommonResult.success(count); + return CommonResult.failed(); + } + + @PostMapping( + value = "/updatePassword", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity updatePasswordPB(InputStream reqStream) throws IOException { + AllProto.UpdatePasswordParamProto updatePasswordParamProto = + AllProto.UpdatePasswordParamProto.parseFrom(reqStream); + UpdatePasswordParam updatePasswordParam = new UpdatePasswordParam(); + updatePasswordParam.setOldPassword(updatePasswordParamProto.getOldPassword()); + updatePasswordParam.setNewPassword(updatePasswordParamProto.getNewPassword()); + + CommonResult result = updatePassword(updatePasswordParam); + + return ProtoUtil.responseInteger(result); + } + + public CommonResult currentUser() { + FrontUser user = userService.getCurrentUser(); + if (user != null) return CommonResult.success(user); + return CommonResult.failed(); + } + + @PostMapping( + value = "/currentUser", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity currentUserPB() { + CommonResult result = currentUser(); + + return ProtoUtil.responseFrontUser(result); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/controller/ArticleController.java b/pro-proto/src/main/java/dr/sbs/proto/controller/ArticleController.java new file mode 100644 index 0000000..c165105 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/controller/ArticleController.java @@ -0,0 +1,161 @@ +package dr.sbs.proto.controller; + +import dr.sbs.common.CommonPage; +import dr.sbs.common.CommonResult; +import dr.sbs.mbg.model.Article; +import dr.sbs.mbg.model.FrontUser; +import dr.sbs.proto.all.AllProto; +import dr.sbs.proto.dto.ArticleCreateParam; +import dr.sbs.proto.service.ArticleService; +import dr.sbs.proto.service.UserService; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("/api/article") +public class ArticleController { + @Autowired private ArticleService articleService; + @Autowired private UserService userService; + + public CommonResult create(ArticleCreateParam articleCreateParam) { + FrontUser user = userService.getCurrentUser(); + if (user == null) { + return CommonResult.unauthorized("Not Logged-in"); + } + + int count = articleService.create(articleCreateParam); + if (count > 0) { + return CommonResult.success(count); + } + return CommonResult.failed(); + } + + @PostMapping( + value = "/create", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity createPB(InputStream reqStream) throws IOException { + AllProto.ArticleCreateParamProto articleCreateParamProto = + AllProto.ArticleCreateParamProto.parseFrom(reqStream); + ArticleCreateParam articleCreateParam = new ArticleCreateParam(); + articleCreateParam.setTitle(articleCreateParamProto.getTitle()); + articleCreateParam.setIntro(articleCreateParamProto.getIntro()); + articleCreateParam.setContent(articleCreateParamProto.getContent()); + + CommonResult result = create(articleCreateParam); + + return ProtoUtil.responseInteger(result); + } + + public CommonResult update(Long id, ArticleCreateParam articleCreateParam) { + FrontUser user = userService.getCurrentUser(); + if (user == null) { + return CommonResult.unauthorized("Not logged in"); + } + + Article article = articleService.getItem(id); + if (!article.getFrontUserId().equals(user.getId())) { + return CommonResult.forbidden("No privileges"); + } + + int count = articleService.update(id, articleCreateParam); + if (count > 0) { + return CommonResult.success(count); + } + return CommonResult.failed(); + } + + @PostMapping( + value = "/update/{id}", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity updatePB(@PathVariable Long id, InputStream reqStream) + throws IOException { + AllProto.ArticleCreateParamProto articleCreateParamProto = + AllProto.ArticleCreateParamProto.parseFrom(reqStream); + ArticleCreateParam articleCreateParam = new ArticleCreateParam(); + articleCreateParam.setTitle(articleCreateParamProto.getTitle()); + articleCreateParam.setIntro(articleCreateParamProto.getIntro()); + articleCreateParam.setContent(articleCreateParamProto.getContent()); + + CommonResult result = update(id, articleCreateParam); + + return ProtoUtil.responseInteger(result); + } + + public CommonResult delete(Long id) { + FrontUser user = userService.getCurrentUser(); + if (user == null) { + return CommonResult.unauthorized("Not logged in"); + } + + Article article = articleService.getItem(id); + if (!article.getFrontUserId().equals(user.getId())) { + return CommonResult.forbidden("No privileges"); + } + + int count = articleService.delete(id); + if (count > 0) { + return CommonResult.success(count); + } + return CommonResult.failed(); + } + + @PostMapping( + value = "/delete", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity deletePB(InputStream reqStream) throws IOException { + AllProto.LongIdParamProto longIdParamProto = AllProto.LongIdParamProto.parseFrom(reqStream); + + CommonResult result = delete(longIdParamProto.getId()); + + return ProtoUtil.responseInteger(result); + } + + public CommonResult> list( + Integer pageSize, Integer pageNum, String searchKey) { + List
queryList = articleService.list(searchKey, pageSize, pageNum); + return CommonResult.success(CommonPage.toPage(queryList)); + } + + @PostMapping( + value = "/list", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity listPB(InputStream reqStream) throws IOException { + AllProto.ListQueryParamProto listQueryParamProto = + AllProto.ListQueryParamProto.parseFrom(reqStream); + + CommonResult> result = + list( + listQueryParamProto.getPageSize(), + listQueryParamProto.getPageNum(), + listQueryParamProto.getSearchKey()); + + return ProtoUtil.responseArticlePage(result); + } + + public CommonResult
record(long id) { + Article article = articleService.getItem(id); + return CommonResult.success(article); + } + + @PostMapping( + value = "/record/{id}", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity recordPB(@PathVariable long id) throws IOException { + CommonResult
result = record(id); + + return ProtoUtil.responseArticle(result); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/controller/CommonController.java b/pro-proto/src/main/java/dr/sbs/proto/controller/CommonController.java new file mode 100644 index 0000000..7cdf0b7 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/controller/CommonController.java @@ -0,0 +1,41 @@ +package dr.sbs.proto.controller; + +import javax.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Slf4j +@Controller +public class CommonController implements ErrorController { + private static final String ERROR_PATH = "/error"; + + @GetMapping(value = ERROR_PATH) + public String handleError(HttpServletRequest request) { + final Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); + + log.error("Error path: [{}], status: [{}]", getErrorPath(), statusCode); + + if (statusCode == 500) { + return "redirect:/500"; + } else { + return "redirect:/404"; + } + } + + @GetMapping(value = "/404") + public String notFround() { + return "error/404"; + } + + @GetMapping(value = "/500") + public String internalError() { + return "error/500"; + } + + @Override + public String getErrorPath() { + return ERROR_PATH; + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/controller/ProtoUtil.java b/pro-proto/src/main/java/dr/sbs/proto/controller/ProtoUtil.java new file mode 100644 index 0000000..ac7377a --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/controller/ProtoUtil.java @@ -0,0 +1,176 @@ +package dr.sbs.proto.controller; + +import cn.hutool.core.date.DateUtil; +import dr.sbs.common.CommonPage; +import dr.sbs.common.CommonResult; +import dr.sbs.mbg.model.Article; +import dr.sbs.mbg.model.FrontUser; +import dr.sbs.proto.all.AllProto; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; + +public class ProtoUtil { + // CommonResult => proto + public static void copyCommonResultProto( + AllProto.IntegerResultProto.Builder builder, CommonResult commonResult) { + builder.setCode(commonResult.getCode()); + builder.setMessage(commonResult.getMessage()); + } + + public static void copyCommonResultProto( + AllProto.ArticleResultProto.Builder builder, CommonResult commonResult) { + builder.setCode(commonResult.getCode()); + builder.setMessage(commonResult.getMessage()); + } + + public static void copyCommonResultProto( + AllProto.FrontUserResultProto.Builder builder, CommonResult commonResult) { + builder.setCode(commonResult.getCode()); + builder.setMessage(commonResult.getMessage()); + } + + public static void copyCommonResultProto( + AllProto.ArticlePageResultProto.Builder builder, CommonResult commonResult) { + builder.setCode(commonResult.getCode()); + builder.setMessage(commonResult.getMessage()); + } + + public static void copyCommonResultProto( + AllProto.FrontUserPageResultProto.Builder builder, CommonResult commonResult) { + builder.setCode(commonResult.getCode()); + builder.setMessage(commonResult.getMessage()); + } + + // CommonPage => proto + public static void copyCommonPageProto( + AllProto.ArticlePageProto.Builder builder, CommonPage commonPage) { + builder.setPageNum(commonPage.getPageNum()); + builder.setPageSize(commonPage.getPageSize()); + builder.setPages(commonPage.getPages()); + builder.setTotal(commonPage.getTotal()); + } + + public static void copyCommonPageProto( + AllProto.FrontUserPageProto.Builder builder, CommonPage commonPage) { + builder.setPageNum(commonPage.getPageNum()); + builder.setPageSize(commonPage.getPageSize()); + builder.setPages(commonPage.getPages()); + builder.setTotal(commonPage.getTotal()); + } + + // FrontUser => proto + public static void copyFrontUserProto( + AllProto.FrontUserProto.Builder builder, FrontUser frontUser) { + if (frontUser == null) return; + + if (frontUser.getId() != null) builder.setId(frontUser.getId()); + if (frontUser.getUsername() != null) builder.setUsername(frontUser.getUsername()); + if (frontUser.getEmail() != null) builder.setEmail(frontUser.getEmail()); + if (frontUser.getPassword() != null) builder.setPassword(frontUser.getPassword()); + if (frontUser.getStatus() != null) builder.setStatus(frontUser.getStatus()); + if (frontUser.getCreateTime() != null) + builder.setCreateTime(DateUtil.formatDateTime(frontUser.getCreateTime())); + if (frontUser.getUpdateTime() != null) + builder.setUpdateTime(DateUtil.formatDateTime(frontUser.getUpdateTime())); + } + + // Article => proto + public static void copyArticleProto(AllProto.ArticleProto.Builder builder, Article article) { + if (article == null) return; + + if (article.getId() != null) builder.setId(article.getId()); + if (article.getTitle() != null) builder.setTitle(article.getTitle()); + if (article.getIntro() != null) builder.setIntro(article.getIntro()); + if (article.getFrontUserId() != null) builder.setFrontUserId(article.getFrontUserId()); + if (article.getReadCount() != null) builder.setReadCount(article.getReadCount()); + if (article.getSupportCount() != null) builder.setSupportCount(article.getSupportCount()); + if (article.getStatus() != null) builder.setStatus(article.getStatus()); + if (article.getCreateTime() != null) + builder.setCreateTime(DateUtil.formatDateTime(article.getCreateTime())); + if (article.getUpdateTime() != null) + builder.setUpdateTime(DateUtil.formatDateTime(article.getUpdateTime())); + if (article.getContent() != null) builder.setContent(article.getContent()); + } + + // response CommonResult + public static ResponseEntity responseInteger(CommonResult result) { + AllProto.IntegerResultProto.Builder builder = AllProto.IntegerResultProto.newBuilder(); + copyCommonResultProto(builder, result); + builder.setData((int) result.getCode()); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); + return new ResponseEntity<>(builder.build().toByteArray(), httpHeaders, HttpStatus.OK); + } + + // response CommonResult + public static ResponseEntity responseFrontUser(CommonResult result) { + AllProto.FrontUserResultProto.Builder builder = AllProto.FrontUserResultProto.newBuilder(); + copyCommonResultProto(builder, result); + AllProto.FrontUserProto.Builder dataBuilder = AllProto.FrontUserProto.newBuilder(); + copyFrontUserProto(dataBuilder, result.getData()); + builder.setData(dataBuilder.build()); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); + return new ResponseEntity<>(builder.build().toByteArray(), httpHeaders, HttpStatus.OK); + } + + // response CommonResult
+ public static ResponseEntity responseArticle(CommonResult
result) { + AllProto.ArticleResultProto.Builder builder = AllProto.ArticleResultProto.newBuilder(); + copyCommonResultProto(builder, result); + AllProto.ArticleProto.Builder dataBuilder = AllProto.ArticleProto.newBuilder(); + copyArticleProto(dataBuilder, result.getData()); + builder.setData(dataBuilder.build()); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); + return new ResponseEntity<>(builder.build().toByteArray(), httpHeaders, HttpStatus.OK); + } + + // response CommonResult> + public static ResponseEntity responseArticlePage( + CommonResult> result) { + AllProto.ArticlePageResultProto.Builder builder = AllProto.ArticlePageResultProto.newBuilder(); + copyCommonResultProto(builder, result); + AllProto.ArticlePageProto.Builder dataBuilder = AllProto.ArticlePageProto.newBuilder(); + copyCommonPageProto(dataBuilder, result.getData()); + + for (Article article : result.getData().getList()) { + AllProto.ArticleProto.Builder itemBuilder = AllProto.ArticleProto.newBuilder(); + copyArticleProto(itemBuilder, article); + dataBuilder.addList(itemBuilder.build()); + } + + builder.setData(dataBuilder.build()); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); + return new ResponseEntity<>(builder.build().toByteArray(), httpHeaders, HttpStatus.OK); + } + + // response CommonResult> + public static ResponseEntity responseFrontUserPage( + CommonResult> result) { + AllProto.FrontUserPageResultProto.Builder builder = + AllProto.FrontUserPageResultProto.newBuilder(); + copyCommonResultProto(builder, result); + AllProto.FrontUserPageProto.Builder dataBuilder = AllProto.FrontUserPageProto.newBuilder(); + copyCommonPageProto(dataBuilder, result.getData()); + + for (FrontUser frontUser : result.getData().getList()) { + AllProto.FrontUserProto.Builder itemBuilder = AllProto.FrontUserProto.newBuilder(); + copyFrontUserProto(itemBuilder, frontUser); + dataBuilder.addList(itemBuilder.build()); + } + + builder.setData(dataBuilder.build()); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); + return new ResponseEntity<>(builder.build().toByteArray(), httpHeaders, HttpStatus.OK); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/controller/RouteController.java b/pro-proto/src/main/java/dr/sbs/proto/controller/RouteController.java new file mode 100644 index 0000000..3355f99 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/controller/RouteController.java @@ -0,0 +1,49 @@ +package dr.sbs.proto.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +@RequestMapping("/") +public class RouteController { + @RequestMapping(value = "", method = RequestMethod.GET) + public String index() { + return "index"; + } + + @RequestMapping(value = "account/login", method = RequestMethod.GET) + public String accountLogin() { + return "account/login"; + } + + @RequestMapping(value = "account/register", method = RequestMethod.GET) + public String accountRegister() { + return "account/register"; + } + + @RequestMapping(value = "account/updatePassword", method = RequestMethod.GET) + public String accountUpdatePassword() { + return "account/updatePassword"; + } + + @RequestMapping(value = "user", method = RequestMethod.GET) + public String user() { + return "user/index"; + } + + @RequestMapping(value = "article/create", method = RequestMethod.GET) + public String articleCreate() { + return "article/create"; + } + + @RequestMapping(value = "article/update/{id}", method = RequestMethod.GET) + public String articleUpdate() { + return "article/update"; + } + + @RequestMapping(value = "article/record/{id}", method = RequestMethod.GET) + public String articleRecord() { + return "article/record"; + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/controller/UserController.java b/pro-proto/src/main/java/dr/sbs/proto/controller/UserController.java new file mode 100644 index 0000000..b414f11 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/controller/UserController.java @@ -0,0 +1,41 @@ +package dr.sbs.proto.controller; + +import dr.sbs.common.CommonPage; +import dr.sbs.common.CommonResult; +import dr.sbs.mbg.model.Article; +import dr.sbs.proto.all.AllProto; +import dr.sbs.proto.service.ArticleService; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("/api/user") +public class UserController { + @Autowired private ArticleService articleService; + + public CommonResult> articles(Integer pageSize, Integer pageNum) { + List
list = articleService.myList(pageSize, pageNum); + return CommonResult.success(CommonPage.toPage(list)); + } + + @PostMapping( + value = "/articles", + headers = {"content-type=application/octet-stream"}) + @ResponseBody + public ResponseEntity articlesPB(InputStream reqStream) throws IOException { + AllProto.ListQueryParamProto listQueryParamProto = + AllProto.ListQueryParamProto.parseFrom(reqStream); + + CommonResult> result = + articles(listQueryParamProto.getPageSize(), listQueryParamProto.getPageNum()); + + return ProtoUtil.responseArticlePage(result); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/dao/.gitkeep b/pro-proto/src/main/java/dr/sbs/proto/dao/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pro-proto/src/main/java/dr/sbs/proto/dto/ArticleCreateParam.java b/pro-proto/src/main/java/dr/sbs/proto/dto/ArticleCreateParam.java new file mode 100644 index 0000000..44cfa0e --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/dto/ArticleCreateParam.java @@ -0,0 +1,24 @@ +package dr.sbs.proto.dto; + +import io.swagger.annotations.ApiModelProperty; +import javax.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; + +/** 文章创建参数 */ +@Getter +@Setter +public class ArticleCreateParam { + // position从10开始,每次增加10,预防中间插入留置 + @ApiModelProperty(value = "标题", required = true, position = 10) + @NotEmpty(message = "标题不能为空") + private String title; + + @ApiModelProperty(value = "简介", required = true, position = 20) + @NotEmpty(message = "简介不能为空") + private String intro; + + @ApiModelProperty(value = "内容", required = true, position = 30) + @NotEmpty(message = "内容不能为空") + private String content; +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/dto/UpdatePasswordParam.java b/pro-proto/src/main/java/dr/sbs/proto/dto/UpdatePasswordParam.java new file mode 100644 index 0000000..3f92425 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/dto/UpdatePasswordParam.java @@ -0,0 +1,19 @@ +package dr.sbs.proto.dto; + +import io.swagger.annotations.ApiModelProperty; +import javax.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; + +/** 修改用户名密码参数 */ +@Getter +@Setter +public class UpdatePasswordParam { + @ApiModelProperty(value = "旧密码", required = true) + @NotEmpty(message = "旧密码不能为空") + private String oldPassword; + + @ApiModelProperty(value = "新密码", required = true) + @NotEmpty(message = "新密码不能为空") + private String newPassword; +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/dto/UserCreateParam.java b/pro-proto/src/main/java/dr/sbs/proto/dto/UserCreateParam.java new file mode 100644 index 0000000..cfd90ab --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/dto/UserCreateParam.java @@ -0,0 +1,24 @@ +package dr.sbs.proto.dto; + +import io.swagger.annotations.ApiModelProperty; +import javax.validation.constraints.NotEmpty; +import lombok.Getter; +import lombok.Setter; + +/** 用户创建参数 */ +@Getter +@Setter +public class UserCreateParam { + // position从10开始,每次增加10,预防中间插入留置 + @ApiModelProperty(value = "用户名", required = true, position = 10) + @NotEmpty(message = "用户名不能为空") + private String username; + + @ApiModelProperty(value = "电子邮箱", required = true, position = 20) + @NotEmpty(message = "电子邮箱不能为空") + private String email; + + @ApiModelProperty(value = "密码", required = true, position = 30) + @NotEmpty(message = "密码不能为空") + private String password; +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/service/ArticleService.java b/pro-proto/src/main/java/dr/sbs/proto/service/ArticleService.java new file mode 100644 index 0000000..aeaf33d --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/service/ArticleService.java @@ -0,0 +1,19 @@ +package dr.sbs.proto.service; + +import dr.sbs.mbg.model.Article; +import dr.sbs.proto.dto.ArticleCreateParam; +import java.util.List; + +public interface ArticleService { + List
list(String searchKey, Integer pageSize, Integer pageNum); + + List
myList(Integer pageSize, Integer pageNum); + + int create(ArticleCreateParam articleCreateParam); + + int update(Long id, ArticleCreateParam articleCreateParam); + + int delete(Long id); + + Article getItem(Long id); +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/service/ArticleServiceImpl.java b/pro-proto/src/main/java/dr/sbs/proto/service/ArticleServiceImpl.java new file mode 100644 index 0000000..25b91f1 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/service/ArticleServiceImpl.java @@ -0,0 +1,103 @@ +package dr.sbs.proto.service; + +import com.github.pagehelper.PageHelper; +import dr.sbs.common.exception.ApiAssert; +import dr.sbs.common.util.UuidUtil; +import dr.sbs.mbg.mapper.ArticleMapper; +import dr.sbs.mbg.model.Article; +import dr.sbs.mbg.model.ArticleExample; +import dr.sbs.mbg.model.FrontUser; +import dr.sbs.proto.dto.ArticleCreateParam; +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +public class ArticleServiceImpl implements ArticleService { + @Autowired private ArticleMapper articleMapper; + @Autowired private UserService userService; + + @Override + public List
list(String searchKey, Integer pageSize, Integer pageNum) { + PageHelper.startPage(pageNum, pageSize); + ArticleExample example = new ArticleExample(); + example.setOrderByClause("id desc"); + ArticleExample.Criteria criteria = example.createCriteria(); + + criteria.andStatusEqualTo((byte) 1); + if (!StringUtils.isEmpty(searchKey)) { + criteria.andTitleLike("%" + searchKey + "%"); + } + return articleMapper.selectByExample(example); + } + + @Override + public List
myList(Integer pageSize, Integer pageNum) { + FrontUser user = userService.getCurrentUser(); + + if (user == null) return new ArrayList<>(); + + PageHelper.startPage(pageNum, pageSize); + ArticleExample example = new ArticleExample(); + example.setOrderByClause("id desc"); + ArticleExample.Criteria criteria = example.createCriteria(); + + criteria.andStatusEqualTo((byte) 1); + criteria.andFrontUserIdEqualTo(user.getId()); + return articleMapper.selectByExample(example); + } + + @Override + public int create(ArticleCreateParam articleCreateParam) { + FrontUser user = userService.getCurrentUser(); + if (user == null) ApiAssert.fail("未登录"); + + Article article = new Article(); + BeanUtils.copyProperties(articleCreateParam, article); + article.setId(UuidUtil.nextId()); + article.setFrontUserId(user.getId()); + return articleMapper.insertSelective(article); + } + + @Override + public int update(Long id, ArticleCreateParam articleCreateParam) { + FrontUser user = userService.getCurrentUser(); + if (user == null) ApiAssert.fail("未登录"); + + Article oldArticle = articleMapper.selectByPrimaryKey(id); + + if (oldArticle == null) ApiAssert.fail("文章不存在"); + if (!oldArticle.getFrontUserId().equals(user.getId())) ApiAssert.fail("无权限"); + + Article article = new Article(); + BeanUtils.copyProperties(articleCreateParam, article); + article.setId(id); + + return articleMapper.updateByPrimaryKeySelective(article); + } + + @Override + public int delete(Long id) { + FrontUser user = userService.getCurrentUser(); + if (user == null) ApiAssert.fail("未登录"); + + Article oldArticle = articleMapper.selectByPrimaryKey(id); + + if (oldArticle == null) ApiAssert.fail("文章不存在"); + if (!oldArticle.getFrontUserId().equals(user.getId())) ApiAssert.fail("无权限"); + + Article article = new Article(); + article.setId(id); + article.setStatus((byte) 0); + + return articleMapper.updateByPrimaryKeySelective(article); + } + + @Override + public Article getItem(Long id) { + return articleMapper.selectByPrimaryKey(id); + } +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/service/UserService.java b/pro-proto/src/main/java/dr/sbs/proto/service/UserService.java new file mode 100644 index 0000000..5dcc0a4 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/service/UserService.java @@ -0,0 +1,22 @@ +package dr.sbs.proto.service; + +import dr.sbs.mbg.model.FrontUser; +import dr.sbs.proto.dto.UpdatePasswordParam; +import dr.sbs.proto.dto.UserCreateParam; + +public interface UserService { + /** Get user by username */ + FrontUser getByUsername(String username); + + /** Get user by id */ + FrontUser getById(Long id); + + /** Sign up */ + FrontUser register(UserCreateParam userCreateParam); + + /** Update password */ + Integer updatePassword(UpdatePasswordParam updatePasswordParam); + + /** Get current user information */ + FrontUser getCurrentUser(); +} diff --git a/pro-proto/src/main/java/dr/sbs/proto/service/UserServiceImpl.java b/pro-proto/src/main/java/dr/sbs/proto/service/UserServiceImpl.java new file mode 100644 index 0000000..7ca5e85 --- /dev/null +++ b/pro-proto/src/main/java/dr/sbs/proto/service/UserServiceImpl.java @@ -0,0 +1,91 @@ +package dr.sbs.proto.service; + +import dr.sbs.common.exception.ApiAssert; +import dr.sbs.common.util.UuidUtil; +import dr.sbs.mbg.mapper.FrontUserMapper; +import dr.sbs.mbg.model.FrontUser; +import dr.sbs.mbg.model.FrontUserExample; +import dr.sbs.proto.bo.UserInfo; +import dr.sbs.proto.dto.UpdatePasswordParam; +import dr.sbs.proto.dto.UserCreateParam; +import java.util.List; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + @Autowired private FrontUserMapper userMapper; + @Autowired private PasswordEncoder passwordEncoder; + + @Override + public FrontUser getByUsername(String username) { + FrontUserExample example = new FrontUserExample(); + example.createCriteria().andUsernameEqualTo(username); + List users = userMapper.selectByExample(example); + if (users.size() > 0) { + return users.get(0); + } + return null; + } + + @Override + public FrontUser getById(Long id) { + return userMapper.selectByPrimaryKey(id); + } + + @Override + public FrontUser register(UserCreateParam userCreateParam) { + // check if the username existed + String username = userCreateParam.getUsername(); + FrontUserExample example = new FrontUserExample(); + example.createCriteria().andUsernameEqualTo(username); + List users = userMapper.selectByExample(example); + if (users.size() > 0) { + ApiAssert.fail("Username '" + username + "' existed."); + } + + // Add the user + FrontUser user = new FrontUser(); + BeanUtils.copyProperties(userCreateParam, user); + user.setId(UuidUtil.nextId()); + user.setUsername(username); + user.setPassword(passwordEncoder.encode(userCreateParam.getPassword())); + userMapper.insertSelective(user); + return user; + } + + @Override + public Integer updatePassword(UpdatePasswordParam updatePasswordParam) { + FrontUser user = getCurrentUser(); + if (user == null) ApiAssert.fail("未登录"); + + if (!passwordEncoder.matches(updatePasswordParam.getOldPassword(), user.getPassword())) { + ApiAssert.fail("原密码错误"); + } + + FrontUser updateUser = new FrontUser(); + updateUser.setId(user.getId()); + updateUser.setPassword(passwordEncoder.encode(updatePasswordParam.getNewPassword())); + + return userMapper.updateByPrimaryKeySelective(updateUser); + } + + @Override + public FrontUser getCurrentUser() { + SecurityContext ctx = SecurityContextHolder.getContext(); + Authentication auth = ctx.getAuthentication(); + + if (auth == null || !auth.isAuthenticated() || auth instanceof AnonymousAuthenticationToken) { + return null; + } + + UserInfo userInfo = (UserInfo) auth.getPrincipal(); + return userInfo.getUser(); + } +} diff --git a/pro-proto/src/main/proto/AllProto.proto b/pro-proto/src/main/proto/AllProto.proto new file mode 100644 index 0000000..84a9c72 --- /dev/null +++ b/pro-proto/src/main/proto/AllProto.proto @@ -0,0 +1,102 @@ +syntax = "proto3"; + +package dr.sbs.proto.all; + +// 因为Web前端不好处理跨文件、import导入的解析,故为了演示, +// 把所有需要的 proto 都放到这个文件中,只用这个文件 + +message ArticleProto { + int64 id = 10; + string title = 20; + string intro = 30; + string content = 40; + int64 frontUserId = 50; + int32 readCount = 60; + int32 supportCount = 70; + int32 status = 80; + string createTime = 90; + string updateTime = 100; +} + +message FrontUserProto { + int64 id = 10; + string username = 20; + string email = 30; + string password = 40; + int32 status = 50; + string createTime = 60; + string updateTime = 70; +} + +message IntegerResultProto { + int64 code = 10; + string message = 20; + int32 data = 30; +} + +message ArticleResultProto { + int64 code = 10; + string message = 20; + ArticleProto data = 30; +} + +message FrontUserResultProto { + int64 code = 10; + string message = 20; + FrontUserProto data = 30; +} + +message ArticlePageProto { + int32 pageNum = 10; + int32 pageSize = 20; + int32 pages = 30; + int64 total = 40; + repeated ArticleProto list = 50; +} + +message FrontUserPageProto { + int32 pageNum = 10; + int32 pageSize = 20; + int32 pages = 30; + int64 total = 40; + repeated FrontUserProto list = 50; +} + +message ArticlePageResultProto { + int64 code = 10; + string message = 20; + ArticlePageProto data = 30; +} + +message FrontUserPageResultProto { + int64 code = 10; + string message = 20; + FrontUserPageProto data = 30; +} + +message LongIdParamProto { + int64 id = 10; +} + +message ListQueryParamProto { + int32 pageSize = 10; + int32 pageNum = 20; + string searchKey = 30; +} + +message ArticleCreateParamProto { + string title = 10; + string intro = 20; + string content = 30; +} + +message UpdatePasswordParamProto { + string oldPassword = 10; + string newPassword = 20; +} + +message UserCreateParamProto { + string username = 10; + string email = 20; + string password = 30; +} diff --git a/pro-proto/src/main/proto/AppProto.proto b/pro-proto/src/main/proto/AppProto.proto new file mode 100644 index 0000000..c212735 --- /dev/null +++ b/pro-proto/src/main/proto/AppProto.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package dr.sbs.proto; + +// protobuf 不支持继承机制,所以想要扩展 ProtoGen.proto 中的 message, +// 只能复制代码,并且每次都要增加或者更改字段,都要同步改复制的代码 + +message ArticleCreateParamProto { + string title = 10; + string intro = 20; + string content = 30; +} + +message UpdatePasswordParamProto { + string oldPassword = 10; + string newPassword = 20; +} + +message UserCreateParamProto { + string username = 10; + string email = 20; + string password = 30; +} diff --git a/pro-proto/src/main/proto/BaseProto.proto b/pro-proto/src/main/proto/BaseProto.proto new file mode 100644 index 0000000..80cdbeb --- /dev/null +++ b/pro-proto/src/main/proto/BaseProto.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package dr.sbs.proto; + +import "google/protobuf/any.proto"; + +message CommonResultProto { + int64 code = 10; + string message = 20; + google.protobuf.Any data = 30; +} + +message CommonPageProto { + int32 pageNum = 10; + int32 pageSize = 20; + int32 pages = 30; + int64 total = 40; + repeated google.protobuf.Any data = 50; +} + +message LongIdParamProto { + int64 id = 10; +} + +message ListQueryParamProto { + int32 pageSize = 10; + int32 pageNum = 20; + string searchKey = 30; +} diff --git a/pro-proto/src/main/proto/ProtoGen.proto b/pro-proto/src/main/proto/ProtoGen.proto new file mode 100644 index 0000000..76d1585 --- /dev/null +++ b/pro-proto/src/main/proto/ProtoGen.proto @@ -0,0 +1,162 @@ +syntax = "proto3"; + +package dr.sbs.protogen; + +message AdminLoginLogProto { + int64 id = 10; + int64 userId = 20; + string ip = 30; + string address = 40; + string userAgent = 50; + string createTime = 60; + string updateTime = 70; +} + +message AdminMenuProto { + int64 id = 10; + int64 parentId = 20; + string title = 30; + int32 level = 40; + int32 sort = 50; + string name = 60; + string icon = 70; + int32 hidden = 80; + string createTime = 90; + string updateTime = 100; +} + +message AdminResourceProto { + int64 id = 10; + string name = 20; + string url = 30; + string description = 40; + int64 categoryId = 50; + string createTime = 60; + string updateTime = 70; +} + +message AdminResourceCategoryProto { + int64 id = 10; + string name = 20; + int32 sort = 30; + string createTime = 40; + string updateTime = 50; +} + +message AdminRoleProto { + int64 id = 10; + string name = 20; + string description = 30; + int32 status = 40; + int32 sort = 50; + string createTime = 60; + string updateTime = 70; +} + +message AdminRoleMenuRelationProto { + int64 id = 10; + int64 roleId = 20; + int64 menuId = 30; + string createTime = 40; + string updateTime = 50; +} + +message AdminRoleResourceRelationProto { + int64 id = 10; + int64 roleId = 20; + int64 resourceId = 30; + string createTime = 40; + string updateTime = 50; +} + +message AdminUserProto { + int64 id = 10; + string username = 20; + string password = 30; + string avatar = 40; + string email = 50; + string nickname = 60; + string note = 70; + string lastLoginTime = 80; + int32 status = 90; + string createTime = 100; + string updateTime = 110; +} + +message AdminUserRoleRelationProto { + int64 id = 10; + int64 userId = 20; + int64 roleId = 30; + string createTime = 40; + string updateTime = 50; +} + +message ArticleProto { + int64 id = 10; + string title = 20; + string intro = 30; + string content = 40; + int64 frontUserId = 50; + int32 readCount = 60; + int32 supportCount = 70; + int32 status = 80; + string createTime = 90; + string updateTime = 100; +} + +message FlywaySchemaHistoryProto { + int32 installedRank = 10; + string version = 20; + string description = 30; + string type = 40; + string script = 50; + int32 checksum = 60; + string installedBy = 70; + string installedOn = 80; + int32 executionTime = 90; + int32 success = 100; +} + +message FrontUserProto { + int64 id = 10; + string username = 20; + string email = 30; + string password = 40; + int32 status = 50; + string createTime = 60; + string updateTime = 70; +} + +message WxPayTransProto { + string transId = 10; + string billNo = 20; + string openId = 30; + string mchId = 40; + string appId = 50; + double totalFee = 60; + double cashFee = 70; + double couponFee = 80; + string createTime = 90; + string updateTime = 100; +} + +message WxUserProto { + int64 id = 10; + string nickname = 20; + string avatar = 30; + int32 gender = 40; + string birthday = 50; + string province = 60; + string city = 70; + string district = 80; + string phone = 90; + string registerDate = 100; + string lastLogin = 110; + string unionId = 120; + string miniOpenId = 130; + string mpOpenId = 140; + int32 status = 150; + string createTime = 160; + string updateTime = 170; +} + diff --git a/pro-proto/src/main/resources/application-dev.yml b/pro-proto/src/main/resources/application-dev.yml new file mode 100644 index 0000000..83a2937 --- /dev/null +++ b/pro-proto/src/main/resources/application-dev.yml @@ -0,0 +1,17 @@ +spring: + devtools: + restart: + enabled: true + + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mysql://127.0.0.1:3306/starter?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: root123456 + + hikari: + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true diff --git a/pro-proto/src/main/resources/application-prod.yml b/pro-proto/src/main/resources/application-prod.yml new file mode 100644 index 0000000..d7a49f4 --- /dev/null +++ b/pro-proto/src/main/resources/application-prod.yml @@ -0,0 +1,15 @@ +spring: + devtools: + restart: + enabled: false + + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mysql://remote-ip:3306/starter?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai + username: root + password: root123456 + +logging: + level: + org.springframework.web: ERROR + dr.sbs: ERROR diff --git a/pro-proto/src/main/resources/application.yml b/pro-proto/src/main/resources/application.yml new file mode 100644 index 0000000..b3bdea7 --- /dev/null +++ b/pro-proto/src/main/resources/application.yml @@ -0,0 +1,47 @@ +server: + port: 9005 + use-forward-headers: true + servlet: + session: + timeout: 86400s + cookie: + http-only: true + compression: + enabled: true + mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain + +spring: + profiles: + active: dev + output: + ansi: + enabled: always + session: + store-type: redis + # thymeleaf: + # if you want to deploy html files separately(not in jar files), uncomment below + # prefix: file:./templates/ + # cache: false + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: Asia/Shanghai + +mybatis: + mapper-locations: + - classpath:dao/**/*.xml + - classpath*:dr/sbs/mbg/mapper/*.xml + +logging: + level: + org.springframework.web: ERROR + dr.sbs: DEBUG + file: ./logs + +app: + # snowflake workerId and dataCenterId, see "hutool IdUtil.createSnowflake(workerId, dataCenterId)" + workerId: 1 + dataCenterId: 1 diff --git a/pro-proto/src/main/resources/dao/.gitkeep b/pro-proto/src/main/resources/dao/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pro-proto/src/main/resources/logback-spring.xml b/pro-proto/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..f5c52de --- /dev/null +++ b/pro-proto/src/main/resources/logback-spring.xml @@ -0,0 +1,31 @@ + + + + + + + + + + ${APP_NAME} + + + + ${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log + 30 + + + ${FILE_LOG_PATTERN} + + + + + + + + + + + + + diff --git a/pro-proto/src/main/resources/templates/account/login.html b/pro-proto/src/main/resources/templates/account/login.html new file mode 100644 index 0000000..50657fa --- /dev/null +++ b/pro-proto/src/main/resources/templates/account/login.html @@ -0,0 +1,43 @@ + + + + + Login + + +

Username:

+

Password:

+

+ +

+ + + diff --git a/pro-proto/src/main/resources/templates/account/register.html b/pro-proto/src/main/resources/templates/account/register.html new file mode 100644 index 0000000..2dbd601 --- /dev/null +++ b/pro-proto/src/main/resources/templates/account/register.html @@ -0,0 +1,183 @@ + + + + + Register + + + + + + +

Email:

+

Username:

+

Password:

+

Confirm Password:

+

+ +

+ + + diff --git a/pro-proto/src/main/resources/templates/account/updatePassword.html b/pro-proto/src/main/resources/templates/account/updatePassword.html new file mode 100644 index 0000000..a0b67b6 --- /dev/null +++ b/pro-proto/src/main/resources/templates/account/updatePassword.html @@ -0,0 +1,176 @@ + + + + + Update Password + + + + + + +

Old Password:

+

New Password:

+

+ +

+ + + diff --git a/pro-proto/src/main/resources/templates/article/create.html b/pro-proto/src/main/resources/templates/article/create.html new file mode 100644 index 0000000..e311021 --- /dev/null +++ b/pro-proto/src/main/resources/templates/article/create.html @@ -0,0 +1,179 @@ + + + + + Create Article + + + + + + +

Title:

+

Article Summary:

+

Article Content:

+

+ +

+ + + diff --git a/pro-proto/src/main/resources/templates/article/record.html b/pro-proto/src/main/resources/templates/article/record.html new file mode 100644 index 0000000..4f3988f --- /dev/null +++ b/pro-proto/src/main/resources/templates/article/record.html @@ -0,0 +1,164 @@ + + + + + Article Detail + + + + + + +

Title:

+

Article Summary:

+

Article Content:

+ + + diff --git a/pro-proto/src/main/resources/templates/article/update.html b/pro-proto/src/main/resources/templates/article/update.html new file mode 100644 index 0000000..a67c315 --- /dev/null +++ b/pro-proto/src/main/resources/templates/article/update.html @@ -0,0 +1,215 @@ + + + + + Update Article + + + + + + +

Title:

+

Article Summary:

+

Article Content:

+

+ +

+ + + diff --git a/pro-proto/src/main/resources/templates/error/404.html b/pro-proto/src/main/resources/templates/error/404.html new file mode 100644 index 0000000..f649524 --- /dev/null +++ b/pro-proto/src/main/resources/templates/error/404.html @@ -0,0 +1,10 @@ + + + + + 404 + + +404 + + diff --git a/pro-proto/src/main/resources/templates/error/500.html b/pro-proto/src/main/resources/templates/error/500.html new file mode 100644 index 0000000..9bbaf86 --- /dev/null +++ b/pro-proto/src/main/resources/templates/error/500.html @@ -0,0 +1,10 @@ + + + + + 500 + + +500 + + diff --git a/pro-proto/src/main/resources/templates/index.html b/pro-proto/src/main/resources/templates/index.html new file mode 100644 index 0000000..4d02e18 --- /dev/null +++ b/pro-proto/src/main/resources/templates/index.html @@ -0,0 +1,247 @@ + + + + + + Spring Boot Starter Protobuf Application + + + + + + +

User Information

+ +
+

Login

+

Sign Up

+
+

Article

+

+ + + + + + + + + + + + + + + +
IdTitleRead CountSupport CountArticle SummaryUser IdCreate TimeUpdate TimeOperations
+ + + diff --git a/pro-proto/src/main/resources/templates/user/index.html b/pro-proto/src/main/resources/templates/user/index.html new file mode 100644 index 0000000..9ac75f3 --- /dev/null +++ b/pro-proto/src/main/resources/templates/user/index.html @@ -0,0 +1,234 @@ + + + + + My Home + + + + + + +

User Information

+

+

Update Password

+

Logout

+

My Articles

+

+ + + + + + + + + + + + + + + +
IdTitleRead CountSupport CountArticle SummaryUser IdCreate TimeUpdate TimeOperations
+ + + diff --git a/pro-protogen/build.gradle b/pro-protogen/build.gradle new file mode 100644 index 0000000..a44b299 --- /dev/null +++ b/pro-protogen/build.gradle @@ -0,0 +1,30 @@ +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' + } +} + +group = 'dr.sbs' +// version should not have '-' +version = new Date().format("yyyy.MMdd.HHmm", TimeZone.getTimeZone("GMT+08:00")) +archivesBaseName = 'protogen' +sourceCompatibility = 1.8 +description = 'Spring Boot Starter Proto Generator Application.' + +mainClassName = 'dr.sbs.protogen.Generator' + +apply plugin: 'com.google.protobuf' + +dependencies { + implementation 'com.google.protobuf:protobuf-java:3.19.1' + implementation 'com.google.protobuf:protobuf-java-util:3.19.1' +} diff --git a/pro-protogen/lombok.config b/pro-protogen/lombok.config new file mode 100644 index 0000000..6aa51d7 --- /dev/null +++ b/pro-protogen/lombok.config @@ -0,0 +1,2 @@ +# This file is generated by the 'io.freefair.lombok' Gradle plugin +config.stopBubbling = true diff --git a/pro-protogen/src/main/java/dr/sbs/protogen/Generator.java b/pro-protogen/src/main/java/dr/sbs/protogen/Generator.java new file mode 100644 index 0000000..662dd4a --- /dev/null +++ b/pro-protogen/src/main/java/dr/sbs/protogen/Generator.java @@ -0,0 +1,190 @@ +package dr.sbs.protogen; + +import cn.hutool.core.io.FileUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Generator { + // 需要生成的包名 + private static String packageName = "dr.sbs.protogen"; + // 生成的文件名 + private static String fileName = "ProtoGen.proto"; + // 表名统一添加后缀 + private static String tableNameSuffix = "Proto"; + // 生成的文件目录,可添加多个(相对于根目录) + private static List dirNames = Arrays.asList("pro-proto/src/main/proto"); + + public static void main(String[] args) throws IOException { + String proDir = System.getProperty("user.dir"); + String rootDir = new File(proDir).getParent(); + + Properties properties = new Properties(); + InputStream in = Generator.class.getResourceAsStream("/mysql.properties"); + properties.load(in); + + // proto 内容 + StringBuilder contentBuilder = new StringBuilder(); + contentBuilder.append("syntax = \"proto3\";\n\npackage ").append(packageName).append(";\n\n"); + + Connection conn = null; + try { + String driverClassName = properties.getProperty("driverClassName"); + String url = properties.getProperty("url"); + String username = properties.getProperty("username"); + String password = properties.getProperty("password"); + + // 注册 JDBC 驱动 + Class.forName(driverClassName); + + // 打开链接 + System.out.println("连接数据库..."); + conn = DriverManager.getConnection(url, username, password); + + // 执行查询 + System.out.println("实例化Statement对象..."); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("show tables"); + System.out.println("查询所有表..."); + // 展开结果集数据库 + while (rs.next()) { + String tableName = rs.getString("Tables_in_starter"); + System.out.println("查询" + tableName + "表字段..."); + contentBuilder + .append("message ") + .append(underlineToCamel(tableName, false)) + .append(tableNameSuffix) + .append(" {\n"); + + Statement stmt2 = conn.createStatement(); + ResultSet rs2 = stmt2.executeQuery("describe " + tableName); + + int idx = 10; + while (rs2.next()) { + String fieldName = rs2.getString("Field"); + String fieldType = rs2.getString("Type"); + + contentBuilder + .append(" ") + .append(mysqlToProtoType(fieldType)) + .append(" ") + .append(underlineToCamel(fieldName, true)) + .append(" = ") + .append(idx) + .append(";\n"); + idx += 10; + } + + contentBuilder.append("}\n\n"); + + rs2.close(); + stmt2.close(); + } + // 完成后关闭 + rs.close(); + stmt.close(); + conn.close(); + } catch (Exception e) { + // 处理 JDBC 错误 + e.printStackTrace(); + } finally { + // 关闭资源 + try { + if (conn != null) conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + for (String dirName : dirNames) { + String filePath = rootDir + "/" + dirName + "/" + fileName; + FileUtil.mkdir(dirName); + FileUtil.writeString(contentBuilder.toString(), filePath, "UTF-8"); + + System.out.println("生成proto文件:" + filePath); + } + + System.out.println("done!"); + } + + /** + * 下划线转驼峰法 + * + * @param line 源字符串 + * @param smallCamel 大小驼峰,是否为小驼峰 + * @return 转换后的字符串 + */ + public static String underlineToCamel(String line, boolean smallCamel) { + if (line == null || "".equals(line)) { + return ""; + } + StringBuffer sb = new StringBuffer(); + Pattern pattern = Pattern.compile("([A-Za-z\\d]+)(_)?"); + Matcher matcher = pattern.matcher(line); + while (matcher.find()) { + String word = matcher.group(); + sb.append( + smallCamel && matcher.start() == 0 + ? Character.toLowerCase(word.charAt(0)) + : Character.toUpperCase(word.charAt(0))); + int index = word.lastIndexOf('_'); + if (index > 0) { + sb.append(word.substring(1, index).toLowerCase()); + } else { + sb.append(word.substring(1).toLowerCase()); + } + } + return sb.toString(); + } + + /** + * "int": "int32", "tinyint": "int32", "smallint": "int32", "mediumint": "int32", "bigint": + * "int64", "varchar": "string", "timestamp": "string", "date": "string", "datetime": "string", + * "text": "string", "double": "double", "decimal": "double", "float": "float", + */ + private static Map mysqlProtoTypeMap = new HashMap<>(); + + static { + mysqlProtoTypeMap.put("int", "int32"); + mysqlProtoTypeMap.put("tinyint", "int32"); + mysqlProtoTypeMap.put("smallint", "int32"); + mysqlProtoTypeMap.put("mediumint", "int32"); + mysqlProtoTypeMap.put("bigint", "int64"); + mysqlProtoTypeMap.put("varchar", "string"); + mysqlProtoTypeMap.put("timestamp", "string"); + mysqlProtoTypeMap.put("date", "string"); + mysqlProtoTypeMap.put("datetime", "string"); + mysqlProtoTypeMap.put("text", "string"); + mysqlProtoTypeMap.put("double", "double"); + mysqlProtoTypeMap.put("decimal", "double"); + mysqlProtoTypeMap.put("float", "float"); + } + + /** + * mysql 数据类型转 proto 数据类型 + * + * @param mysqlType mysql 数据类型 + * @return proto 数据类型 + */ + public static String mysqlToProtoType(String mysqlType) throws Exception { + // 去掉括号 + String pureMysqlType = mysqlType.split("\\(")[0]; + String protoType = mysqlProtoTypeMap.get(pureMysqlType); + if (protoType == null) throw new Exception("未识别mysql字段:" + mysqlType); + return protoType; + } +} diff --git a/pro-protogen/src/main/resources/logback-spring.xml b/pro-protogen/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..c878432 --- /dev/null +++ b/pro-protogen/src/main/resources/logback-spring.xml @@ -0,0 +1,31 @@ + + + + + + + + + + ${APP_NAME} + + + + ${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log + 30 + + + ${FILE_LOG_PATTERN} + + + + + + + + + + + + + diff --git a/pro-protogen/src/main/resources/mysql.properties b/pro-protogen/src/main/resources/mysql.properties new file mode 100644 index 0000000..ad9d3d8 --- /dev/null +++ b/pro-protogen/src/main/resources/mysql.properties @@ -0,0 +1,4 @@ +driverClassName=com.mysql.cj.jdbc.Driver +url=jdbc:mysql://127.0.0.1:3306/starter?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true +username=root +password=root123456 diff --git a/settings.gradle b/settings.gradle index a1acb9a..df4ba11 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,5 +8,7 @@ include ':pro-rws' include ':pro-mdb' include ':pro-mdbmbg' include ':pro-amqp' +include ':pro-proto' +include ':pro-protogen' rootProject.name = 'spring-boot-starter'