Commit 3d957700 by liboyang

项目初始化

parents
#gralde build
build
*/build
gradle
.gradle
classes
gradlew*
#idea
.idea
*.iml
out
#java
*.class
#*.jar
*.war
*.ear
visual-cloud_log/
#elastic
data
#vscode
.classpath
.project
*/bin
*/.settings
.settings
\ No newline at end of file
plugins {
id 'org.springframework.boot' version '2.1.5.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.maxrocky'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
druidVersion = '1.1.5'
mybatisVersion = '1.3.1'
pagehelperVersion='4.1.0'
EncodeVersion='4.11'
orikaVersion = '1.5.2'
fastJsonVersion = '1.2.41'
lombokVersion = '1.16.18'
swaggerVersion='2.7.0'
langVersion='3.7'
collectsVersion='3.2.2'
beanUtilsVersion='1.9.3'
ioVersion='2.6'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
//校验
compile('org.springframework.boot:spring-boot-starter-validation')
//热更新
runtime('org.springframework.boot:spring-boot-devtools')
//事物注解
compile ('org.springframework:spring-tx:4.3.10.RELEASE')
//mybatis
compile("org.mybatis.spring.boot:mybatis-spring-boot-starter:${mybatisVersion}")
compile("com.alibaba:druid:$druidVersion")
compile("com.github.pagehelper:pagehelper:${pagehelperVersion}")
runtime('mysql:mysql-connector-java')
//日志所需jar
compile("net.logstash.logback:logstash-logback-encoder:$EncodeVersion")
compile group: 'org.slf4j', name: 'log4j-over-slf4j', version: '1.7.25'
//aop
compile('org.springframework.boot:spring-boot-starter-aop')
//swagger依赖
compile("io.springfox:springfox-swagger-ui:${swaggerVersion}")
compile("io.springfox:springfox-swagger2:${swaggerVersion}")
compile("ma.glasnost.orika:orika-core:$orikaVersion")
compile("org.projectlombok:lombok:$lombokVersion")
compile("com.alibaba:fastjson:$fastJsonVersion")
compile("org.apache.commons:commons-lang3:$langVersion")
compile("commons-collections:commons-collections:$collectsVersion")
compile("commons-beanutils:commons-beanutils:$beanUtilsVersion")
compile("commons-io:commons-io:$ioVersion")
//springboot jacson支持jsr310 data/time
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
pluginManagement {
repositories {
gradlePluginPortal()
}
}
rootProject.name = 'springboot2-demo'
package com.maxrocky;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.maxrocky.repository")
public class VisualCloudApplication {
public static void main(String[] args) {
SpringApplication.run(VisualCloudApplication.class, args);
}
}
package com.maxrocky.common.config;
import org.springframework.context.annotation.Configuration;
/**
* @author beyondLi
* @desc 普通配置.
*/
@Configuration
public class CommonConfiguration {
}
package com.maxrocky.common.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* @author beyondLi
* @desc 解决跨域问题
*/
@Configuration
public class CrossFilter extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
String[] origins = {"*"};
registry.addMapping("/**")
.allowedOrigins(origins)
.allowCredentials(true)
.allowedMethods("*")
.maxAge(3600);
}
}
package com.maxrocky.common.config;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
/**
* @author beyondLi
* @desc Swagger配置类.
*/
@EnableSwagger2//Swagger的开关,表示我们在项目中启用Swagger
@Configuration//声名这是一个配置类
//@ConfigurationProperties(prefix = "swagger")
@ConditionalOnProperty(prefix = "swagger",value = {"enable"}, havingValue = "true")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SwaggerConfig {
//controller接口所在的包
@Value("${swagger.basePackage}")
private String basePackage;
//当前文档的标题
@Value("${swagger.title}")
private String title;
//当前文档的详细描述
@Value("${swagger.description}")
private String description;
//当前文档的版本
@Value("${swagger.version}")
private String version;
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage(basePackage))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(title)
.description(description)
.version(version)
.build();
}
}
\ No newline at end of file
package com.maxrocky.common.config;
import com.github.pagehelper.PageHelper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* Created by beyondLi
*/
@Configuration
public class UnitPage {
/**
* 注册MyBatis分页插件PageHelper
* @return
*/
@Bean
public PageHelper pageHelper() {
PageHelper pageHelper = new PageHelper();
Properties p = new Properties();
p.setProperty("offsetAsPageNum", "true");
p.setProperty("rowBoundsWithCount", "true");
p.setProperty("reasonable", "true");
pageHelper.setProperties(p);
return pageHelper;
}
}
package com.maxrocky.common.tools.aop;
import lombok.extern.java.Log;
import org.aspectj.lang.JoinPoint;
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.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
/**
* @author beyondLi
* @desc 打印controller的请求信息.
*/
@Component
@Aspect
@Log
public class ControllerMethodParamLog {
/**
* 配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
*/
@Pointcut("execution(* com.maxrocky.controller..*(..))")
public void aspect() {
}
/**
* 配置前置通知,使用在方法aspect()上注册的切入点
* @param point 切入点
*/
@Before("aspect()")
public void before(JoinPoint point) {
//获取方法
MethodSignature signature = (MethodSignature) point.getSignature();
//访问路径
List<Annotation> annotations = Arrays.asList(signature.getMethod().getDeclaredAnnotations())
.stream()
.filter(annotation -> annotation.annotationType() == RequestMapping.class)
.collect(Collectors.toList());
String requestPath = annotations.size() >= 1
? signature.getMethod().getAnnotation(RequestMapping.class).value()[0] : "";
String className = point.getSignature().getDeclaringType().getSimpleName();
String methodName = point.getSignature().getName();
//请求路径,类名,方法名,参数列表
log.log(Level.INFO, "\n---------请求路径: " + requestPath
+ "\n---------类名: " + className
+ "\n---------方法: " + methodName
+ "\n---------参数: {" + getArgs(point) + "}");
}
private String getArgs(JoinPoint point) {
String[] parameterNames = ((MethodSignature) point.getSignature()).getParameterNames();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < parameterNames.length; i++) {
sb.append(parameterNames[i] + ":" + point.getArgs()[i].toString() + "; ");
}
return sb.toString();
}
}
package com.maxrocky.common.tools.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author beyondLi
* @date 2017/9/27 11:03
* @desc aop拦截demo
*/
@Component
//开关
@Aspect
public class ExampleAop {
@Around("execution (* com.maxrocky.controller..*.*(..))")
public Object beforeCheckToken(ProceedingJoinPoint pro) throws Throwable {
/**
//设置Token值
String token = null;
//从cookie中取出所有属性值
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Cookie[] cookie = request.getCookies(); //获取token
String requestAddress = request.getRequestURI(); //取出请求地址
if (cookie != null) {
for (Cookie index : cookie) {
if (index.getName().equals("BACK_TOKEN")) //取出token值
token = index.getValue();
}
}
**/
//方法执行完成后执行的方法
Object proceed = pro.proceed();
return proceed;
}
}
package com.maxrocky.common.tools.exception;
import com.maxrocky.common.utils.uuid.UUIDUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author beyondLi
* @desc 异常工厂.
*/
@Component
@PropertySource(value = "exception.properties", encoding = "UTF-8")
public class ExceptionManager {
@Value("${spring.application.name}")
private String appName;
@Value("${spring.cloud.client.ipAddress}")
private String ip;
@Resource
Environment environment;
/**
* 创建默认的异常
* @param code
* @return
*/
public PhantomException createByCode(String code) {
return new PhantomException(UUIDUtils.getUUID(), appName, ip, code, environment.getProperty(code));
}
public PhantomException createByMessage(String message) {
return new PhantomException(UUIDUtils.getUUID(), appName, ip, "系统异常", message);
}
/**
* 简化异常栈信息
* @param pe
* @return
*/
protected PhantomException create(PhantomException pe) {
List<StackTraceElement> traceList = Stream.of(pe.getStackTrace())
.filter(p -> p.getClassName().contains("com.maxrocky"))
.filter(p -> !p.getClassName().contains("$"))
.filter(p -> !p.getClassName().contains(".exception."))
.collect(Collectors.toList());
pe.setStackTrace(traceList.toArray(new StackTraceElement[]{}));
return pe;
}
}
package com.maxrocky.common.tools.exception;
import com.maxrocky.common.utils.uuid.UUIDUtils;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.annotation.Resource;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
/**
* @author beyondLi
* @desc 全局异常捕捉并转换异常
*/
@Log4j
@RestControllerAdvice(basePackages = "com.maxrocky.controller")
public class GlobalExceptionHandler {
@Value("${spring.application.name}")
private String appName;
@Value("${spring.cloud.client.ipAddress}")
private String ip;
@Resource
ExceptionManager exceptionManager;
/**
* 校验异常
* @param e
*/
@ExceptionHandler(ConstraintViolationException.class)
public String handlerException(ConstraintViolationException e) {
String code = e.getConstraintViolations().stream().limit(1).map(vio -> vio.getMessageTemplate())
.collect(Collectors.toList()).get(0);
PhantomException exception = exceptionManager.createByCode(code);
exception.setStackTrace(e.getStackTrace());
PhantomException phantomException = exceptionManager.create(exception);
log.error(logTraceInfo(phantomException));
return phantomException.toString();
}
/**
* 如果是自定义异常
* @param e
*/
@ExceptionHandler(PhantomException.class)
public String handlerException(PhantomException e) {
PhantomException phantomException = exceptionManager.create(e);
log.error(logTraceInfo(phantomException));
return phantomException.toString();
}
@ExceptionHandler(NonTransientDataAccessException.class)
public String handleException(NonTransientDataAccessException e) {
PhantomException exception = new PhantomException(UUIDUtils.getUUID(), appName, ip, "SQL_ERR", "SQL_ERROR");
exception.setStackTrace(e.getStackTrace());
PhantomException phantomException = exceptionManager.create(exception);
log.error(logTraceInfo(phantomException));
log.error(logTraceInfo(e));
return phantomException.toString();
}
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
PhantomException exception = new PhantomException(UUIDUtils.getUUID(), appName, ip, "SYSTEM_ERR", "STSTEM_ERROR");
exception.setStackTrace(e.getStackTrace());
PhantomException phantomException = exceptionManager.create(exception);
log.error(logTraceInfo(phantomException));
log.error(logTraceInfo(e));
return phantomException.toString();
}
/**
* 实现异常栈信息打印,通过查看源码
* @param e
* @return
*/
private String logTraceInfo(Exception e) {
StackTraceElement[] trace = e.getStackTrace();
StringBuilder sb = new StringBuilder();
sb.append(e);
for (StackTraceElement traceElement : trace) {
sb.append("\n\t " + traceElement);
}
return sb.toString();
}
}
package com.maxrocky.common.tools.exception;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author beyondLi
* @desc 自定义异常.
*/
@Data
@NoArgsConstructor
public class PhantomException extends RuntimeException {
public PhantomException(String id, String appName, String ip, String code, String msg) {
this.id = id;
this.appName = appName;
this.ip = ip;
this.code = code;
this.msg = msg;
}
//异常id
private String id;
private String appName;
private String ip;
//错误码
private String code;
//错误提示信息
private String msg;
@Override
public String toString() {
return String.format("{\"appName\":\"%s\",\"code\":\"%s\",\"id\":\"%s\",\"ip\":\"%s\",\"msg\":\"%s\"}",
this.appName, this.code, this.id, this.ip, this.msg);
}
}
package com.maxrocky.common.utils.apiresult;
import lombok.Data;
/**
* @author beyondLi
* @desc 返回体.
*/
@Data
public abstract class AbstractApiResult {
protected String code;
/**
* 成功的返回
* @param data 数据
* @return 正常返回体
*/
public static AbstractApiResult success(Object data) {
return new SuccessApiResult(data);
}
/**
* 错误返回
* @param errorCode 错误码
* @param errorMessage 错误信息
* @return 错误返回体
*/
public static AbstractApiResult error(String errorCode, String errorMessage) {
return new ErrorApiResult(errorCode, errorMessage);
}
}
package com.maxrocky.common.utils.apiresult;
import lombok.Data;
/**
* @author beyondLi
* @desc 错误返回.
*/
@Data
public class ErrorApiResult extends AbstractApiResult {
private String msg;
ErrorApiResult(String code, String msg) {
this.code = code;
this.msg = msg;
}
}
package com.maxrocky.common.utils.apiresult;
import lombok.Data;
/**
* @author beyondLi
* @desc 正确返回体
*/
@Data
public class SuccessApiResult extends AbstractApiResult {
private Object data;
SuccessApiResult(Object data) {
this.code = "0";
this.data = data;
}
}
package com.maxrocky.common.utils.date;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Date;
/**
* @author beyondLi
* @desc java8日期工具.
*/
public final class LocalDateTimeUtils {
private LocalDateTimeUtils() { }
//获取当前时间的LocalDateTime对象
//LocalDateTime.now();
//根据年月日构建LocalDateTime
//LocalDateTime.of();
//比较日期先后
//LocalDateTime.now().isBefore(),
//LocalDateTime.now().isAfter(),
//Date转换为LocalDateTime
public static LocalDateTime convertDateToLDT(Date date) {
return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
}
//LocalDateTime转换为Date
public static Date convertLDTToDate(LocalDateTime time) {
return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
}
//获取指定日期的毫秒
public static Long getMilliByTime(LocalDateTime time) {
return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}
//获取指定日期的秒
public static Long getSecondsByTime(LocalDateTime time) {
return time.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
}
//获取指定时间的指定格式
public static String formatTime(LocalDateTime time, String pattern) {
return time.format(DateTimeFormatter.ofPattern(pattern));
}
//获取当前时间的指定格式
public static String formatNow(String pattern) {
return formatTime(LocalDateTime.now(), pattern);
}
//日期加上一个数,根据field不同加不同值,field为ChronoUnit.*
public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) {
return time.plus(number, field);
}
//日期减去一个数,根据field不同减不同值,field参数为ChronoUnit.*
public static LocalDateTime minu(LocalDateTime time, long number, TemporalUnit field) {
return time.minus(number, field);
}
/**
* 获取两个日期的差 field参数为ChronoUnit.*
* @param startTime
* @param endTime
* @param field 单位(年月日时分秒)
* @return
*/
public static long betweenTwoTime(LocalDateTime startTime, LocalDateTime endTime, ChronoUnit field) {
Period period = Period.between(LocalDate.from(startTime), LocalDate.from(endTime));
if (field == ChronoUnit.YEARS) {
return period.getYears();
}
if (field == ChronoUnit.MONTHS) {
return period.getYears() * 12 + period.getMonths();
}
return field.between(startTime, endTime);
}
//获取一天的开始时间,2017,7,22 00:00
public static LocalDateTime getDayStart(LocalDateTime time) {
return time.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0);
}
//获取一天的结束时间,2017,7,22 23:59:59.999999999
public static LocalDateTime getDayEnd(LocalDateTime time) {
return time.withHour(23)
.withMinute(59)
.withSecond(59)
.withNano(999999999);
}
}
package com.maxrocky.common.utils.json;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author beyondLi
* @desc Json工具.
*/
public final class JsonUtils {
private JsonUtils() { }
/**
* json串转换为对象
* @param json
* @param clazz
* @param <T>
* @return
*/
public static <T> T jsonToBean(String json, Class<T> clazz) {
return JSON.parseObject(json, clazz);
}
/**
* 对象转换为json
* @param object
* @return
*/
public static String beanToJson(Object object) {
return JSON.toJSONString(object);
}
/**
* 对象转换为json,可以带上date的格式化
* @param object
* @return
*/
public static String beanToJson(Object object, String dateFormat) {
if (Objects.isNull(dateFormat) || "".equals(dateFormat)) {
return JSON.toJSONString(object);
}
return JSON.toJSONStringWithDateFormat(object, dateFormat);
}
/**
* json返回List
* @param arrayJson
* @param clazz
* @param <T>
* @return
*/
public static <T> List<T> jsonToList(String arrayJson, Class<T> clazz, String dateFormat) {
String temp = JSONObject.DEFFAULT_DATE_FORMAT;
if (!"".equals(dateFormat) && dateFormat != null) {
JSONObject.DEFFAULT_DATE_FORMAT = dateFormat;
}
List<T> list = JSON.parseArray(arrayJson, clazz);
JSONObject.DEFFAULT_DATE_FORMAT = temp;
return list;
}
/**
* 反序列化Map
* @param mapJson
* @param <K>
* @param <V>
* @return
*/
public static <K, V> Map jsonMap(String mapJson, Class<K> keyType, Class<V> valueType) {
return JSON.parseObject(mapJson, new TypeReference<Map<K, V>>() { });
}
}
package com.maxrocky.common.utils.mapper;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author beyondLi
* @desc 属性映射工具.
*/
public final class MapperUtils {
private MapperUtils() { }
/**
* 构建一个Mapper工厂
*/
private static final MapperFactory MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();
/**
* 将s属性映射到R的具体实例上
* @param s 已有的Bean,源Bean
* @param rClass
* @param <S> sourceBean
* @param <R> ReturnBean
* @return R的实例
*/
public static <S, R> R mapperBean(S s, Class<R> rClass) {
return MAPPER_FACTORY.getMapperFacade().map(s, rClass);
}
/**
* 将s属性映射到R的具体实例上,如果转换的属性名不一样,可以传入Map进行说明
* @param s 已有的Bean,源Bean
* @param rClass
* @param <S> sourceBean
* @param <R> ReturnBean
* @return R的实例
*/
public static <S, R> R mapperBean(S s, Class<R> rClass, Map<String, String> diffFieldMap) {
ClassMapBuilder<?, R> classMap = MAPPER_FACTORY.classMap(s.getClass(), rClass);
diffFieldMap.forEach(classMap::field);
classMap.byDefault()
.register();
return MAPPER_FACTORY.getMapperFacade().map(s, rClass);
}
/**
* 将s的集合射成R的集合
* @param sList 已有的Bean的集合
* @param rClass 要转换的类型
* @param <S> sourceBean
* @param <R> ReturnBean
* @return R的实例
*/
public static <S, R> List<R> mapperList(List<S> sList, Class<R> rClass) {
return MAPPER_FACTORY.getMapperFacade().mapAsList(sList, rClass);
}
/**
* 将s的集合射成R的集合,不同的属性通过Map<String, String> 传入
* @param sList 已有的Bean的集合
* @param rClass 要转换的类型
* @param <S> sourceBean
* @param <R> ReturnBean
* @return R的实例
*/
public static <S, R> List<R> mapperList(List<S> sList, Class<R> rClass, Map<String, String> diffFieldMap) {
if (sList.isEmpty()) {
return Collections.emptyList();
}
ClassMapBuilder<?, R> classMap = MAPPER_FACTORY.classMap(sList.get(0).getClass(), rClass);
diffFieldMap.forEach(classMap::field);
classMap.byDefault()
.register();
return MAPPER_FACTORY.getMapperFacade().mapAsList(sList, rClass);
}
}
package com.maxrocky.common.utils.md5;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
/**
* @author beyondLi
* @desc MD5工具类, 全部接收UTF编码的String
*/
public final class MD5Utils {
private MD5Utils() { }
private static final String ALGORITHM_MD5 = "MD5";
private static final String UTF_8 = "UTF-8";
/**
* MD5 16bit 小写.
* @param readyEncryptStr ready encrypt string
* @return String encrypt result string
* @throws NoSuchAlgorithmException
* */
public static String md5Bit16Lower(String readyEncryptStr) throws Exception {
if (Objects.nonNull(readyEncryptStr)) {
return MD5Utils.md5Bit32Lower(readyEncryptStr).substring(8, 24);
} else {
return null;
}
}
/**
* MD5 16bit 大写.
* @param readyEncryptStr ready encrypt string
* @return String encrypt result string
* @throws NoSuchAlgorithmException
* */
public static String md5Bit16Upper(String readyEncryptStr) throws Exception {
return md5Bit16Lower(readyEncryptStr).toUpperCase();
}
/**
* MD5 32bit 小写.
* @param readyEncryptStr ready encrypt string
* @return String encrypt result string
* @throws NoSuchAlgorithmException
* */
public static String md5Bit32Lower(String readyEncryptStr) throws Exception {
if (Objects.nonNull(readyEncryptStr)) {
MessageDigest md = MessageDigest.getInstance(ALGORITHM_MD5);
md.update(readyEncryptStr.getBytes(UTF_8));
byte[] b = md.digest();
StringBuilder su = new StringBuilder();
for (int offset = 0, bLen = b.length; offset < bLen; offset++) {
String haxHex = Integer.toHexString(b[offset] & 0xFF);
if (haxHex.length() < 2) {
su.append("0");
}
su.append(haxHex);
}
return su.toString();
} else {
return null;
}
}
/**
* MD5 32bit 大写.
* @param readyEncryptStr ready encrypt string
* @return String encrypt result string
* @throws NoSuchAlgorithmException
* */
public static String md5Bit32Upper(String readyEncryptStr) throws Exception {
return md5Bit32Lower(readyEncryptStr).toUpperCase();
}
}
package com.maxrocky.common.utils.page;
/**
* @author beyondLi
* @desc 分页请求参数封装.
*/
public class PageParam {
public static final int PAGE_SIZE = 10;
public PageParam() {
this.c = PAGE_SIZE;
}
public PageParam(Integer p, Integer c) {
this.setP(p);
this.setC(c);
}
//当前页
private Integer p = 1;
//每页容量
private Integer c;
public Integer getP() {
return p;
}
public void setP(Integer p) {
if (p != null && p > 0) {
this.p = p;
}
}
public Integer getC() {
return c;
}
public void setC(Integer c) {
if (c != null) {
this.c = c;
}
}
}
package com.maxrocky.common.utils.page;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author beyondLi
* @param <T> 实体类型
* @desc 分页容器.
*/
@Getter @Setter
public class PageResult<T> {
private PageResult() { }
//当前页
private Integer page;
//总数量
private long count;
//分页数据
private List<T> list;
public PageResult(int page, long count, List<T> list) {
this.page = page;
this.count = count;
this.list = list;
}
}
package com.maxrocky.common.utils.page;
import com.maxrocky.common.utils.mapper.MapperUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author beyondLi
* @desc PageResul工厂.
*/
@Component
public class PageResultFactory {
public <T> PageResult createPageResult(int page, int count, List<T> data) {
return new PageResult<T>(page, count, data);
}
public <E> PageResult<E> convert(PageResult pageResult, Class<E> dtoClass) {
List<E> dtoList = MapperUtils.mapperList(pageResult.getList(), dtoClass);
//+1是因为hibernate分页从0开始
return new PageResult<E>(pageResult.getPage() + 1, pageResult.getCount(), dtoList);
}
public <T, E> PageResult<E> convert(PageResult<T> pageResult, Function<T, E> function) {
return new PageResult<E>(pageResult.getPage(), pageResult.getCount(),
pageResult.getList().stream().map(function).collect(Collectors.toList()));
}
}
package com.maxrocky.common.utils.random;
import java.util.Random;
/**
* @author beyondLi
* @desc 随机生成工具类.
*/
public final class RandomUtils {
private RandomUtils() { }
private static Random random = new Random();
/**
* 获取一个固定长度的随机整数,可当做验证码。
* @param size 长度
* @return String
*/
public static String getRandomNum(int size) {
return String.valueOf(Math.random()).substring(2, size + 2);
}
/**
* 获取两个数的中间数,包含min和max
* @param min 最小
* @param max 最大
* @return 中间数
*/
public static int getMidNum(int min, int max) {
return min + random.nextInt(max - min + 1);
}
}
package com.maxrocky.common.utils.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author beyondLi
* @desc 正则.
*/
public final class RegexUtils {
private static Pattern mobile = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$");
private static Pattern chinese = Pattern.compile("[\u4e00-\u9fa5]");
private RegexUtils() { }
/**
* @param mobiles
* @return isMobileNO
* @description 校验手机号是否正确
*/
public static boolean isMobileNO(String mobiles) {
Matcher m = mobile.matcher(mobiles);
return m.matches();
}
/**
* @param email
* @return isEmail
* @description 校验邮箱是否正确
*/
public static boolean isEmail(String email) {
String str = "^([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)*@([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[\\.][A-Za-z]{2,3}([\\.][A-Za-z]{2})?$";
Pattern p = Pattern.compile(str);
Matcher m = p.matcher(email);
return m.matches();
}
/**
* @param value
* @return isInteger
* @description 校验是否是整数
*/
public static boolean isInteger(String value) {
try {
Integer.parseInt(value);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* 判断是否含有特殊字符
* @param text
* @return boolean true,通过,false,没通过
*/
public static boolean hasSpecialChar(String text) {
if (null == text || "".equals(text)) {
return true;
}
if (text.replaceAll("[a-z]*[A-Z]*\\d*-*_*\\s*", "").length() == 0) {
// 如果不包含特殊字符
return false;
}
return true;
}
/**
* 判断是否正整数
* @param number 数字
* @return boolean true,通过,false,没通过
*/
public static boolean isNumber(String number) {
if (null == number || "".equals(number)) {
return false;
}
String regex = "[0-9]*";
return number.matches(regex);
}
/**
* 判断是否是正确的IP地址
* @param ip
* @return boolean true,通过,false,没通过
*/
public static boolean isIp(String ip) {
if (null == ip || "".equals(ip)) {
return false;
}
String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." + "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
return ip.matches(regex);
}
/**
* 判断是否含有中文,仅适合中国汉字,不包括标点
* @param text
* @return boolean true,通过,false,没通过
*/
public static boolean isChinese(String text) {
if (null == text || "".equals(text)) {
return false;
}
Matcher m = chinese.matcher(text);
return m.find();
}
}
package com.maxrocky.common.utils.uuid;
import java.util.ArrayList;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author beyondLi
* @desc uuid生成.
*/
public final class UUIDUtils {
private UUIDUtils() { }
/**
* 获取UUID,不含有-
* @return
*/
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 批量获取UUID
* @param size
* @return
*/
public static ArrayList<String> getUUIDList(int size) {
return Stream.iterate(1, item -> item + 1)
.limit(size)
.map(item -> getUUID())
.collect(Collectors.toCollection(ArrayList::new));
}
}
package com.maxrocky.controller.test;
import com.maxrocky.common.utils.apiresult.AbstractApiResult;
import com.maxrocky.common.utils.page.PageParam;
import com.maxrocky.common.utils.page.PageResult;
import com.maxrocky.entity.dto.test.AddUserInfoDTO;
import com.maxrocky.service.test.TestService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* Created by beyondLi
* Date 2019/6/4 14:10
* Desc .
*/
@RestController
@Api(value = "test", tags = {"swagger测试展示"})
@RequestMapping(value = "/test")
public class TestController {
@Resource
TestService testService;
@RequestMapping(value = "/get/info", method = RequestMethod.GET)
@ApiOperation(value = "分页展示", produces = "application/json")
public PageResult getInfo(@ModelAttribute PageParam pageParam) {
PageResult pageResult = testService.getInfo(pageParam);
return pageResult;
}
@RequestMapping(value = "/err/info", method = RequestMethod.GET)
@ApiOperation(value = "异常展示", produces = "application/json")
public AbstractApiResult errInfo() {
testService.errInfo();
return AbstractApiResult.success("ok");
}
@RequestMapping(value = "/add/info", method = RequestMethod.POST)
@ApiOperation(value = "新增展示", produces = "application/json")
public AbstractApiResult addInfo(@RequestBody AddUserInfoDTO addUserInfoDTO) {
testService.addInfo(addUserInfoDTO);
return AbstractApiResult.success("ok");
}
}
package com.maxrocky.entity.dto.test;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* Created by beyondLi
* Date 2019/6/4 14:24
* Desc .
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AddUserInfoDTO {
//姓名
@ApiModelProperty(value="用户名",name="name",example="beyondLi")
@NotBlank(message = "USER_0001")
private String name;
//年龄
@ApiModelProperty(value="年龄",name="age",example="18")
@NotNull(message = "USER_0002")
private Integer age;
}
package com.maxrocky.entity.po.test;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* Created by beyondLi
* Date 2019/6/4 14:24
* Desc .
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AddUserInfoPO {
//id
private String id;
//姓名
private String name;
//年龄
private Integer age;
}
package com.maxrocky.entity.po.test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Created by beyondLi
* Date 2019/6/4 14:24
* Desc .
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfoPO {
//id
private String id;
//姓名
private String name;
//年龄
private Integer age;
}
package com.maxrocky.entity.vo.test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Created by beyondLi
* Date 2019/6/4 14:24
* Desc .
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfoVO {
//id
private String id;
//姓名
private String name;
//年龄
private Integer age;
}
package com.maxrocky.repository.test;
import com.maxrocky.entity.po.test.AddUserInfoPO;
import com.maxrocky.entity.po.test.UserInfoPO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* Created by beyondLi
* Date 2019/6/4 14:19
* Desc .
*/
public interface TestCUDMapper {
/**
* 新增用户信息
* @param addUserInfoPO
*/
@Insert("INSERT INTO user_info (id, name, age) VALUES (#{id},#{name}, #{age})")
void addInfo(AddUserInfoPO addUserInfoPO);
}
package com.maxrocky.repository.test;
import com.maxrocky.entity.po.test.UserInfoPO;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* Created by beyondLi
* Date 2019/6/4 14:19
* Desc .
*/
public interface TestQueryMapper {
/**
* 查看用户信息
* @return
*/
@Select("select id, name, age from user_info")
List<UserInfoPO> getInfo();
}
package com.maxrocky.service.test;
import com.maxrocky.common.utils.page.PageParam;
import com.maxrocky.common.utils.page.PageResult;
import com.maxrocky.entity.dto.test.AddUserInfoDTO;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
/**
* Created by beyondLi
* Date 2019/6/4 14:14
* Desc .
*/
@Validated
public interface TestService {
/**
* 查看用户信息
* @param pageParam
* @return
*/
PageResult getInfo(PageParam pageParam);
/**
* 测试异常
*/
void errInfo();
/**
* 新增用户信息
* @param addUserInfoDTO
*/
void addInfo(@Valid AddUserInfoDTO addUserInfoDTO);
}
package com.maxrocky.service.test;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.maxrocky.common.tools.exception.ExceptionManager;
import com.maxrocky.common.utils.mapper.MapperUtils;
import com.maxrocky.common.utils.page.PageParam;
import com.maxrocky.common.utils.page.PageResult;
import com.maxrocky.common.utils.page.PageResultFactory;
import com.maxrocky.common.utils.uuid.UUIDUtils;
import com.maxrocky.entity.dto.test.AddUserInfoDTO;
import com.maxrocky.entity.po.test.AddUserInfoPO;
import com.maxrocky.entity.po.test.UserInfoPO;
import com.maxrocky.entity.vo.test.UserInfoVO;
import com.maxrocky.repository.test.TestCUDMapper;
import com.maxrocky.repository.test.TestQueryMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
/**
* Created by beyondLi
* Date 2019/6/4 14:14
* Desc .
*/
@Service
public class TestServiceImpl implements TestService {
@Resource
TestQueryMapper testQueryMapper;
@Resource
TestCUDMapper testCUDMapper;
@Resource
PageResultFactory pageResultFactory;
@Resource
ExceptionManager exceptionManager;
/**
* 获取用户分页信息
* @param pageParam
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public PageResult getInfo(PageParam pageParam) {
//设置分页
PageHelper.startPage(pageParam.getP(), pageParam.getC());
List<UserInfoPO> userInfoPOList = testQueryMapper.getInfo();
// 取分页信息
int total = (int) new PageInfo(userInfoPOList).getTotal();
List<UserInfoVO> userInfoVOS = MapperUtils.mapperList(userInfoPOList, UserInfoVO.class);
PageResult pageResult = pageResultFactory.createPageResult(pageParam.getP(), total, userInfoVOS);
return pageResult;
}
/**
* 异常测试
*/
@Override
@Transactional(readOnly = true)
public void errInfo() {
if (true) {
throw exceptionManager.createByCode("TEST_0001");
}
}
/**
* 新增用户信息
* @param addUserInfoDTO
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void addInfo(AddUserInfoDTO addUserInfoDTO) {
AddUserInfoPO addUserInfoPO = MapperUtils.mapperBean(addUserInfoDTO, AddUserInfoPO.class);
addUserInfoPO.setId(UUIDUtils.getUUID());
testCUDMapper.addInfo(addUserInfoPO);
}
}
#=================================基础配置========================================================
server:
port: 8088
spring:
cloud:
client:
ipAddress: 127.0.0.1
application:
name: maxrocky
#=================================数据库配置========================================================
datasource:
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
schema: classpath:schema-maxrocky.sql
minIdle: 5
validationQuery: select 'x'
initialSize: 5
maxWait: 60000
poolPreparedStatements: true
filters: stat,wall,slf4j
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost/maxrocky?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2b8
username: root
password: 123456
platform: mysql
maxPoolPreparedStatementPerConnectionSize: 20
testOnBorrow: false
testWhileIdle: true
minEvictableIdleTimeMillis: 30000
timeBetweenEvictionRunsMillis: 60000
testOnReturn: false
driver-class-name: com.mysql.cj.jdbc.Driver
maxActive: 300
jpa:
show-sql: true
hibernate:
ddl-auto: none
#=================================其他配置========================================================
#解决mybaits传入空值问题
mybatis:
configuration:
jdbc-type-for-null: NULL
map-underscore-to-camel-case: true
#配置swagger
swagger:
basePackage: com.maxrocky.controller
description: visual-cloud
title: visual-cloud
version: V1.0
enable: true
TEST_0001=测试异常
USER_0001=用户名不能为空
USER_0002=年龄不能为空
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--日志级别动态修改功能-->
<!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
<contextName>${HOSTNAME}</contextName>
<property name="LOG_PATH" value="maxrocky-log" />
<springProperty scope="context" name="appName" source="spring.application.name" />
<springProperty scope="context" name="ip" source="spring.cloud.client.ipAddress" />
<property name="CONSOLE_LOG_PATTERN"
value="[%d{yyyy-MM-dd HH:mm:ss.SSS} ${ip} ${appName} %highlight(%-5level) %yellow(%X{X-B3-TraceId}),%green(%X{X-B3-SpanId}),%blue(%X{X-B3-ParentSpanId}) %yellow(%thread) %green(%logger) %msg%n"/>
<appender name="FILEERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>../${LOG_PATH}/${appName}/${appName}-error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>../${LOG_PATH}/${appName}/${appName}-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="FILEWARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>../${LOG_PATH}/${appName}/${appName}-warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>../${LOG_PATH}/${appName}/${appName}-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="FILEINFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>../${LOG_PATH}/${appName}/${appName}-info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>../${LOG_PATH}/${appName}/${appName}-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<append>true</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>../${LOG_PATH}/${appName}/${appName}.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>../${LOG_PATH}/${appName}/${appName}-%d{yyyy-MM-dd}.json</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<pattern>
<pattern>
{
"ip": "${ip}",
"app": "${appName}",
"level": "%level",
"trace": "%X{X-B3-TraceId:-}",
"span": "%X{X-B3-SpanId:-}",
"parent": "%X{X-B3-ParentSpanId:-}",
"thread": "%thread",
"class": "%logger{40}",
"message": "%message",
"stack_trace": "%exception{10}"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
</appender>
<logger name="org.springframework" level="INFO" />
<logger name="org.hibernate" level="INFO" />
<logger name="com.maxrocky.controller" level="DEBUG" />
<root level="INFO">
<appender-ref ref="FILEERROR" />
<appender-ref ref="FILEWARN" />
<appender-ref ref="FILEINFO" />
<appender-ref ref="logstash" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
### 使用说明
#### 一、环境准备
本项目基于jdk8以及gradle4.0以上版本
1.此demo使用前需要先本地安装好mysql数据库,并手动创建maxrocky库(mysql 账号/密码 root/123456)
2.在数据库中执行src -> main -> resources -> schema-maxrocky.sql 中的sql语句
3.在idea中安装lombok插件
4. File -> setting -> Annotation Processors 中 勾选Enable annotation processing
5.启动项目,在浏览器中访问 http://localhost:8088/swagger-ui.html
#### 二、项目说明
本项目仅作参考,具体规范可根据各项目组实际要求做调整,其中包含分页、异常、swagger、lombok等常用工具编写demo
##### java
src -> main -> java -> com -> maxrocky -> common -> utils 此目录为项目常用工具类,可熟悉。
src -> main -> java -> com -> maxrocky -> controller 此目录编写controller层业务逻辑
src -> main -> java -> com -> maxrocky -> service 此目录编写service层业务逻辑
src -> main -> java -> com -> maxrocky -> repository 此目录编写repository层业务逻辑
src -> main -> java -> com -> maxrocky -> entity 此目录存放实体类
##### resources
src -> main -> resource -> application.yml 配置文件
src -> main -> resource -> exception.properties 异常信息编写
src -> main -> resource -> readme.txt 说明书
#### 三、其他
VO:给前端需要展示的数据
DTO:代表需要前端传过来,要接受的数据
PO:与数据库做交互的类
CREATE TABLE `user_info` (
`id` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'id',
`name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
`age` int(11) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
\ No newline at end of file
package com.maxrocky.visualcloud;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class VisualCloudApplicationTests {
@Test
public void contextLoads() {
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment