commit bbb638c28aa49a4b213ff4c11e631df03983e6ec Author: zhansihu Date: Thu Sep 21 15:44:11 2023 +0800 init: 初始化项目 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a66b8e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +application-local.yml +*.log + +rebel.xml +.flattened-pom.xml \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c43bc4c --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +Riven-瑞雯 +-------------------------- + +## 能力 +* 钉钉/企微 组织人员同步 + +## 本地运行参数 +* 系统环境变量 +server.port=8080;spring.datasource.url=jdbc:mysql://116.63.13.181:3311/pudge-dev?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&useSSL=true&verifyServerCertificate=false&rewriteBatchedStatements=true;CUSTOM_ENV=dev \ No newline at end of file diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..a033ae0 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,2 @@ +# 发布记录 + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5a76d0c --- /dev/null +++ b/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + + cn.axzo.infra + axzo-parent + 2.4.13.5 + + + cn.axzo + riven + pom + ${revision} + riven + + + 2.0.0-SNAPSHOT + 2.0.0-SNAPSHOT + 1.18.22 + 1.4.2.Final + 2.0.0-SNAPSHOT + + + + riven-server + riven-api + + + + + + + cn.axzo.infra + axzo-bom + ${axzo-bom.version} + pom + import + + + cn.axzo.infra + axzo-dependencies + ${axzo-dependencies.version} + pom + import + + + + + + + + + org.projectlombok + lombok + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + junit + junit + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + + + + axzo + axzo repository + https://nexus.axzo.cn/repository/axzo/ + + + diff --git a/riven-api/pom.xml b/riven-api/pom.xml new file mode 100644 index 0000000..e9486df --- /dev/null +++ b/riven-api/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + riven + cn.axzo + ${revision} + ../pom.xml + + + riven-api + jar + riven-api + + + + cn.axzo.framework + axzo-consumer-spring-cloud-starter + + + diff --git a/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeApi.java b/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeApi.java new file mode 100644 index 0000000..7f361d6 --- /dev/null +++ b/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeApi.java @@ -0,0 +1,33 @@ +package cn.axzo.riven.client.feign; + +import cn.axzo.framework.domain.page.PageQO; +import cn.axzo.framework.domain.web.result.ApiPageResult; +import cn.axzo.framework.domain.web.result.ApiResult; +import cn.axzo.riven.client.model.NewUserReq; +import cn.axzo.riven.client.model.UpdateUserReq; +import cn.axzo.riven.client.model.UserRes; +import cn.azxo.framework.common.model.CommonResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +/** + * @Author: liyong.tian + * @Date: 2022/9/17 + * @Description: + */ +@FeignClient(name = "riven", url = "http://localhost:8899", fallbackFactory = MicroArchetypeFallbackFactory.class) +public interface MicroArchetypeApi { + + @PostMapping(value = "/api/v1/users", consumes = APPLICATION_JSON_VALUE) + CommonResponse createUser(@RequestBody NewUserReq req); + + @PutMapping(value = "/api/v2/users/{id}", consumes = APPLICATION_JSON_VALUE) + ApiResult updateUser(@PathVariable("id") Long id, @RequestBody UpdateUserReq req); + + @GetMapping(value = "/api/v2/users") + ApiPageResult fetchUsers(@RequestParam Map query, PageQO page); +} diff --git a/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeApiFallback.java b/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeApiFallback.java new file mode 100644 index 0000000..9b9c917 --- /dev/null +++ b/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeApiFallback.java @@ -0,0 +1,52 @@ +package cn.axzo.riven.client.feign; + +import cn.axzo.framework.client.feign.FeignFallback; +import cn.axzo.framework.domain.page.PageQO; +import cn.axzo.framework.domain.web.result.ApiPageResult; +import cn.axzo.framework.domain.web.result.ApiResult; +import cn.axzo.riven.client.model.NewUserReq; +import cn.axzo.riven.client.model.UpdateUserReq; +import cn.axzo.riven.client.model.UserRes; +import cn.azxo.framework.common.model.CommonResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; + +/** + * @Author: liyong.tian + * @Date: 2022/9/17 + * @Description: + */ +@Slf4j +@RequiredArgsConstructor +public class MicroArchetypeApiFallback implements MicroArchetypeApi { + + private final FeignFallback fallback; + + /** + * 老项目迁移使用 + * @param req + * @return + */ + @Override + public CommonResponse createUser(NewUserReq req) { + log.error("[riven-api] createUser fallback", fallback.getCause()); + return CommonResponse.error("创建用户失败"); + } + + /** + * 新项目推荐使用 + */ + @Override + public ApiResult updateUser(Long id, UpdateUserReq req) { + log.error("[riven-api] updateUser fallback", fallback.getCause()); + return fallback.resp(); + } + + @Override + public ApiPageResult fetchUsers(Map query, PageQO page) { + log.error("[riven-api] fetchUsers fallback", fallback.getCause()); + return fallback.pageResp(); + } +} diff --git a/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeFallbackFactory.java b/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeFallbackFactory.java new file mode 100644 index 0000000..9dc4086 --- /dev/null +++ b/riven-api/src/main/java/cn/axzo/riven/client/feign/MicroArchetypeFallbackFactory.java @@ -0,0 +1,19 @@ +package cn.axzo.riven.client.feign; + +import cn.axzo.framework.client.feign.FeignFallback; +import cn.axzo.framework.domain.web.code.IRespCode; +import cn.axzo.framework.domain.web.code.RespCode; +import feign.hystrix.FallbackFactory; +import org.springframework.stereotype.Component; + +@Component +public class MicroArchetypeFallbackFactory implements FallbackFactory { + + // TODO: 2022/11/3 100-调整为具体的项目编号,XXX-调整为项目名 + private final IRespCode respCode = new RespCode("100" + "91001", "XXX服务不可用"); + + @Override + public MicroArchetypeApiFallback create(Throwable cause) { + return new MicroArchetypeApiFallback(new FeignFallback(cause, respCode)); + } +} diff --git a/riven-api/src/main/java/cn/axzo/riven/client/model/NewUserReq.java b/riven-api/src/main/java/cn/axzo/riven/client/model/NewUserReq.java new file mode 100644 index 0000000..8a69abe --- /dev/null +++ b/riven-api/src/main/java/cn/axzo/riven/client/model/NewUserReq.java @@ -0,0 +1,29 @@ +package cn.axzo.riven.client.model; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @Author: liyong.tian + * @Date: 2022/9/2 + * @Description: + */ +@Data +public class NewUserReq { + + @NotBlank(message = "名称不能为空") + private String name; + + @NotNull + private Integer sex; + + private Integer age; + + private String phone; + + private String email; + + private String address; +} diff --git a/riven-api/src/main/java/cn/axzo/riven/client/model/QueryUserReq.java b/riven-api/src/main/java/cn/axzo/riven/client/model/QueryUserReq.java new file mode 100644 index 0000000..6747dd2 --- /dev/null +++ b/riven-api/src/main/java/cn/axzo/riven/client/model/QueryUserReq.java @@ -0,0 +1,22 @@ +package cn.axzo.riven.client.model; + +import cn.axzo.framework.context.client.IQueryMap; +import cn.axzo.framework.context.client.QueryMap; +import lombok.Data; + +@Data +public class QueryUserReq implements IQueryMap { + + private Long id; + + private String name; + + private String phone; + + private String email; + + @Override + public void append(QueryMap.Builder builder) { + builder.put("id", id).put("name", name).put("phone", phone).put("email", email); + } +} diff --git a/riven-api/src/main/java/cn/axzo/riven/client/model/UpdateUserReq.java b/riven-api/src/main/java/cn/axzo/riven/client/model/UpdateUserReq.java new file mode 100644 index 0000000..24c4107 --- /dev/null +++ b/riven-api/src/main/java/cn/axzo/riven/client/model/UpdateUserReq.java @@ -0,0 +1,19 @@ +package cn.axzo.riven.client.model; + +import lombok.Data; + +@Data +public class UpdateUserReq { + + private String name; + + private Integer sex; + + private Integer age; + + private String phone; + + private String email; + + private String address; +} diff --git a/riven-api/src/main/java/cn/axzo/riven/client/model/UserRes.java b/riven-api/src/main/java/cn/axzo/riven/client/model/UserRes.java new file mode 100644 index 0000000..fdf14cd --- /dev/null +++ b/riven-api/src/main/java/cn/axzo/riven/client/model/UserRes.java @@ -0,0 +1,21 @@ +package cn.axzo.riven.client.model; + +import lombok.Data; + +@Data +public class UserRes { + + private Long id; + + private String name; + + private Integer sex; + + private Integer age; + + private String phone; + + private String email; + + private String address; +} diff --git a/riven-api/src/test/java/cn/axzo/maven/archetype/client/AppTest.java b/riven-api/src/test/java/cn/axzo/maven/archetype/client/AppTest.java new file mode 100644 index 0000000..7d836fa --- /dev/null +++ b/riven-api/src/test/java/cn/axzo/maven/archetype/client/AppTest.java @@ -0,0 +1,14 @@ +package cn.axzo.maven.archetype.client; + +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Unit test for simple App. + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class AppTest { + +} diff --git a/riven-server/pom.xml b/riven-server/pom.xml new file mode 100644 index 0000000..c5151be --- /dev/null +++ b/riven-server/pom.xml @@ -0,0 +1,66 @@ + + + + riven + cn.axzo + ${revision} + ../pom.xml + + 4.0.0 + + riven-server + jar + + riven-server + + + + true + true + true + + + + + cn.axzo + riven-api + ${project.version} + + + cn.axzo.framework + axzo-web-spring-boot-starter + + + cn.axzo.framework + axzo-spring-cloud-starter + + + cn.axzo.framework + axzo-consumer-spring-cloud-starter + + + cn.axzo.framework + axzo-processor-spring-boot-starter + + + + cn.axzo.framework + axzo-mybatisplus-spring-boot-starter + + + + cn.axzo.framework + axzo-logger-spring-boot-starter + + + + cn.hutool + hutool-all + + + + mysql + mysql-connector-java + + + diff --git a/riven-server/src/main/java/cn/axzo/riven/Application.java b/riven-server/src/main/java/cn/axzo/riven/Application.java new file mode 100644 index 0000000..1821424 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/Application.java @@ -0,0 +1,36 @@ +package cn.axzo.riven; + +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; + +@Slf4j +@MapperScan(value = {"cn.axzo.riven.repository.mapper"}) +@SpringBootApplication(scanBasePackages = "cn.axzo") +public class Application { + public static void main(String[] args) { + ConfigurableApplicationContext run = SpringApplication.run(Application.class, args); + Environment env = run.getEnvironment(); + log.info( + "--------------------------------------------------------------------------------------------------------------------\n" + + "Application 【{}】 is running on 【{}】 environment!\n" + + "Api Local: \thttp://127.0.0.1:{}\n" + + "Mysql: \t{}\t username:{}\n" + + "Redis: \t{}:{}\t database:{}\n" + + "RabbitMQ: \t{}\t username:{}", + env.getProperty("spring.application.name"), + env.getProperty("spring.profiles.active"), + env.getProperty("server.port"), + env.getProperty("spring.datasource.url"), + env.getProperty("spring.datasource.username"), + env.getProperty("spring.redis.host"), + env.getProperty("spring.redis.port"), + env.getProperty("spring.redis.database"), + env.getProperty("spring.rabbitmq.addresses"), + env.getProperty("spring.rabbitmq.username") + + "\n----------------------------------------------------------"); + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/common/enums/ErrorCode.java b/riven-server/src/main/java/cn/axzo/riven/common/enums/ErrorCode.java new file mode 100644 index 0000000..25a7f27 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/common/enums/ErrorCode.java @@ -0,0 +1,27 @@ +package cn.axzo.riven.common.enums; + +import cn.axzo.framework.domain.web.code.IProjectRespCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: 响应码规范:一共8位,取值范围0~9,3位项目编号(首位不能为0)+2位模块编号+3位自定义编号 + */ +@Getter +@AllArgsConstructor +public enum ErrorCode implements IProjectRespCode { + + USER_NOT_EXISTS("01001", "用户不存在,id=%s"), + USER_PHONE_EMAIL_IS_NULL("01002", "电话和邮箱不能都为空"); + + private String code; + private String message; + + @Override + public String getProjectCode() { + // 根据不同项目进行项目编码调整,可联系框架组获取项目编号(首位不能为0) + return "100"; + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/common/enums/ResultCode.java b/riven-server/src/main/java/cn/axzo/riven/common/enums/ResultCode.java new file mode 100644 index 0000000..5ec2bff --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/common/enums/ResultCode.java @@ -0,0 +1,88 @@ +package cn.axzo.riven.common.enums; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +public enum ResultCode { + /** + * 成功 [GET] + */ + SUCCESS(200), + /** + * [POST/PUT/PATCH] 用户新建或修改数据成功 + */ + CREATED(201), + /** + * [*] 标识一个请求已经进入后台排队 (异步任务) + */ + ACCEPTED(202), + /** + * [DELETE]: 用户删除数据成功 + */ + NO_CONTENT(204), + /** + * [POST/PUT/PATCH] 用户发出的请求有错误, 服务器没有进行新建或修改数据的操作, 该操作是幂等的. + */ + FAIL(400), + /** + * [*] 标识没有权限 (令牌、用户名、密码错误) + */ + UNAUTHORIZED(401), + /** + * [*] 标识用户得到授权(与401错误相对), 但是访问是被禁止的 + */ + FORBIDDEN(403), + /** + * [*] 用户发出的请求针对的是不存在的记录, 服务器没有进行操作 + */ + NOT_FOUND(404), + /** + * [GET] 用户请求的格式不可得 (比如用户请求JSON格式, 但是只有XML格式) + */ + NOT_ACCEPTABLE(406), + /** + * [GET] 用户请求的资源被永久删除, 且不会再得到 + */ + GONE(410), + /** + * [POST/PUT/PATCH] 当创建一个对象时, 发生一个验证错误646 + */ + UNPROCESSABLE_ENTITY(422), + /** + * 服务器内部错误 + */ + INTERNAL_SERVER_ERROR(9999), + /** + * 通用业务异常 + */ + SERVICE_EXCEPTION_ERROR(9998), + + /** + * ####业务的响应码#### + * 按业务依次划分 : + * 一共6位, 第6位是业务代码 第1-5位响应码, 按业务不同码不同 + * #100000 全局级别 + */ + + /** + * 100001 当前用户被强制下线 + */ + CUSTOM_100001(100001), + /** + * 确认弹窗响应码 + */ + CUSTOM_100002(100002); + + public int code; + + ResultCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + +} diff --git a/riven-server/src/main/java/cn/axzo/riven/common/util/package-info.java b/riven-server/src/main/java/cn/axzo/riven/common/util/package-info.java new file mode 100644 index 0000000..c0a1a7d --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/common/util/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.common.util; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/config/FeignConfiguration.java b/riven-server/src/main/java/cn/axzo/riven/config/FeignConfiguration.java new file mode 100644 index 0000000..29d668a --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/config/FeignConfiguration.java @@ -0,0 +1,173 @@ +package cn.axzo.riven.config; + +import cn.axzo.framework.domain.ServiceException; +import cn.azxo.framework.common.constatns.Constants; +import cn.hutool.core.util.ReflectUtil; +import com.google.common.collect.Lists; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import feign.Target.HardCodedTarget; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +/** + * @Author: liyong.tian + * @Date: 2023/8/29 14:50 + * @Description: + */ +@Slf4j +@Configuration +public class FeignConfiguration implements RequestInterceptor { + + @Autowired + private Environment environment; + + private static final String FEIGN_URL_REGEX = "http://([A-Za-z-|_]+)(:[1-9]\\d{1,4}[/]?$)?"; + private static final String LOCAL_HOST = "localhost"; + private static final String DEV_HOST = "http://dev-app.axzo.cn/"; + private static final String TEST_HOST = "http://test-api.axzo.cn/"; + private static final String TEST1_HOST = "http://test1-api.axzo.cn/"; + private static final String PRE_HOST = "http://pre-api.axzo.cn/"; + private static final String PRE_NEW_HOST = "http://pre-new-api.axzo.cn/"; + private static final String UAT_HOST = "http://uat-api.axzo.cn/"; + private static final List HOSTS = Lists + .newArrayList(LOCAL_HOST, DEV_HOST, TEST_HOST, TEST1_HOST, PRE_HOST, PRE_NEW_HOST, UAT_HOST); + + private static String POD_NAMESPACE; + + static { + Map env = System.getenv(); + if (env != null) { + POD_NAMESPACE = env.get("MY_POD_NAMESPACE"); + } + log.info("init FeignConfig, POD_NAMESPACE value is {}", POD_NAMESPACE); + } + + @SneakyThrows + @Override + public void apply(RequestTemplate requestTemplate) { + // POD_NAMESPACE为空 说明服务没有运行在rancher中则进行url替换;POD_NAMESPACE不为空时服务间通过Istio通信则不需要替换url + if (POD_NAMESPACE == null) { + HardCodedTarget target = (HardCodedTarget) requestTemplate.feignTarget(); + Field field = ReflectUtil.getField(target.getClass(), "url"); + field.setAccessible(true); + String url = (String) field.get(target); + // 如需要调用开发者本地启动的xxx服务,传入到toLocalServiceNames + List toLocalServiceNames = Lists.newArrayList(""); + String convertUrl = convertUrl(url, toLocalServiceNames); + field.set(target, convertService(convertUrl)); + target.apply(requestTemplate); + } + requestTemplate.header(Constants.CTX_LOG_ID_MDC, MDC.get(Constants.CTX_LOG_ID_MDC)); + requestTemplate.header("X-SERVER-NAME", environment.getProperty("spring.application.name")); + } + + /** + * 某些服务URL需要特殊处理 + */ + private String convertService(String url) { + if (url.contains("data-collection")) { + return url.replaceAll("data-collection", "dataCollection"); + } else if (url.contains("iot-hub")) { + return url.replaceAll("iot-hub", "iotHub"); + } + return url; + } + + /** + * 将feign api中的url替换为其他环境 + */ + private String convertUrl(String url, List toLocalServiceNames) { + // 已经替换过的 不再进行替换了 + if (HOSTS.stream().anyMatch(url::contains)) { + return url; + } + String convertUrl = toLocalHost(url, toLocalServiceNames); + if (!convertUrl.equals(url)) { + return convertUrl; + } + // 环境为空或为生产环境,不替换 + String profile = environment.getProperty("spring.profiles.active"); + if (profile == null) { + return url; + } + + // 调用profile对应环境的服务 + switch (profile.toLowerCase()) { + case "local": + return toLocalHost(url); + case "dev": + return toRemoteHost(url, DEV_HOST); + case "test": + return toRemoteHost(url, TEST_HOST); + case "test1": + return toRemoteHost(url, TEST1_HOST); + case "pre": + return toRemoteHost(url, PRE_HOST); + case "pre-new": + return toRemoteHost(url, PRE_NEW_HOST); + case "uat": + return toRemoteHost(url, UAT_HOST); + default: + throw new ServiceException("不支持该环境的feign api调用!"); + } + } + + /** + * 调用本地启动的服务 + * + * @param url 原url + * @param toLocalServiceNames 需要切换到本地的服务名 + */ + private static String toLocalHost(String url, List toLocalServiceNames) { + if (CollectionUtils.isEmpty(toLocalServiceNames)) { + return url; + } + if (toLocalServiceNames.stream().anyMatch(o -> StringUtils.isNotBlank(o) && url.contains(o))) { + return toLocalHost(url); + } + return url; + } + + /** + * 调用本地启动的服务,如:http://apollo:11000 -> http://localhost:11000 + */ + private static String toLocalHost(String url) { + String serviceName = getServiceNameFromUrl(url); + // 替换服务名为localhost + return serviceName == null ? url : url.replaceAll(serviceName, LOCAL_HOST); + } + + /** + * 调用远程指定环境的服务,如:http://apollo:11000 -> http://dev-app.axzo.cn/apollo + */ + private static String toRemoteHost(String url, String host) { + String serviceName = getServiceNameFromUrl(url); + // 拼接远端host到url + return serviceName == null ? url : host + serviceName; + } + + /** + * 提取url中的服务名 + */ + private static String getServiceNameFromUrl(String url) { + Pattern pattern = Pattern.compile(FEIGN_URL_REGEX); + Matcher matcher = pattern.matcher(url); + String serviceName = null; + if (matcher.matches()) { + serviceName = matcher.group(1); + } + return serviceName; + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/config/filter/HttpTraceLogFilter.java b/riven-server/src/main/java/cn/axzo/riven/config/filter/HttpTraceLogFilter.java new file mode 100644 index 0000000..7f93b4b --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/config/filter/HttpTraceLogFilter.java @@ -0,0 +1,259 @@ +package cn.axzo.riven.config.filter; + +import cn.azxo.framework.common.constatns.Constants; +import cn.azxo.framework.common.utils.LogUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONException; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.google.common.base.Strings; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.apache.skywalking.apm.toolkit.trace.Trace; +import org.apache.skywalking.apm.toolkit.trace.TraceContext; +import org.slf4j.MDC; +import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; +import org.springframework.web.util.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * @Author: liyong.tian + * @Date: 2022/12/6 14:48 + * @Description: Http接口日志记录 + */ +@Slf4j +@Component +public class HttpTraceLogFilter extends OncePerRequestFilter implements Ordered { + + private static final String X_REQUEST_ID = "x-request-id"; + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 10; + } + + @Override + @Trace(operationName = "HttpTraceLogFilter") + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String uri = request.getRequestURI(); + String contextPath = request.getContextPath(); + String url = uri.substring(contextPath.length()); + //swagger 跳过 + if (url.contains("api-docs") || url.contains("swagger") || url.contains("checkDeath")) { + filterChain.doFilter(request, response); + return; + } + //静态资源 跳过 + if (url.contains(".")) { + filterChain.doFilter(request, response); + return; + } + if (!(request instanceof ContentCachingRequestWrapper)) { + request = new ContentCachingRequestWrapper(request); + } + if (!(response instanceof ContentCachingResponseWrapper)) { + response = new ContentCachingResponseWrapper(response); + } + + String requestId = request.getHeader(X_REQUEST_ID); + if (Strings.isNullOrEmpty(requestId)) { + MDC.put(X_REQUEST_ID, getTraceId()); + } else { + MDC.put(X_REQUEST_ID, requestId); + } + String ctxLogId = request.getHeader(Constants.CTX_LOG_ID_MDC); + if (Strings.isNullOrEmpty(ctxLogId)) { + MDC.put(Constants.CTX_LOG_ID_MDC, getTraceId()); + } else { + MDC.put(Constants.CTX_LOG_ID_MDC, ctxLogId); + } + + // 获取请求参数 + String parameter = null; + String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE); + if (requestContentType != null) { + if (requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE)) { + //Json + WrappedHttpServletRequest requestWrapper = new WrappedHttpServletRequest(request); + parameter = getRequestBody(requestWrapper); + request = requestWrapper; + } else if (requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) { + //普通表单提交 + parameter = JSON.toJSONString(request.getParameterMap()); + } else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) { + //文件表单提交 + parameter = JSON.toJSONString("文件类型"); + } + } else if (url.startsWith("/api")) { + if ("GET".equals(request.getMethod())) { + parameter = JSON.toJSONString(request.getParameterMap()); + } else if ("POST".equals(request.getMethod())) { + WrappedHttpServletRequest requestWrapper = new WrappedHttpServletRequest(request); + parameter = getRequestBody(requestWrapper); + request = requestWrapper; + } + } else if ("GET".equals(request.getMethod())) { + parameter = JSON.toJSONString(request.getParameterMap()); + } + + long requestTime = System.currentTimeMillis(); + try { + filterChain.doFilter(request, response); + } finally { + response.setHeader(Constants.CTX_LOG_ID_MDC, MDC.get(Constants.CTX_LOG_ID_MDC)); + response.setHeader(X_REQUEST_ID, MDC.get(Constants.CTX_LOG_ID_MDC)); + + long latency = System.currentTimeMillis() - requestTime; + String responseBody = null; + int responseStatus = response.getStatus(); + String responseContentType = response.getContentType(); + //Json + if (responseContentType != null && responseContentType + .startsWith(MediaType.APPLICATION_JSON_VALUE)) { + responseBody = getResponseBody(response); + } + //记录日志 + HttpTraceLog traceLog = new HttpTraceLog(); + traceLog.setRequestContentType(requestContentType); + traceLog.setPath(url); + traceLog.setMethod(request.getMethod()); + traceLog.setTimeTaken(latency); + traceLog.setParameter(parameter); + traceLog.setResponseContentType(responseContentType); + traceLog.setStatus(responseStatus); + traceLog.setResponseBody(responseBody); + traceLog.setRequestHeaders(getRequestHeader(request)); + if (traceLog.getResponseCode() != null && traceLog.getResponseCode().equals(9999)) { + LogUtil.error(LogUtil.ErrorLevel.P0, LogUtil.ErrorType.ERROR_BUSINESS, JSON.toJSONString(traceLog)); + } else if (traceLog.getResponseCode() != null && traceLog.getResponseCode().equals(9998)) { + log.warn(JSON.toJSONString(traceLog)); + } else { + log.info(JSON.toJSONString(traceLog)); + } + updateResponse(response); + // 清理链路id + MDC.clear(); + } + } + + private String getTraceId() { + String contextTraceId = TraceContext.traceId(); + return Strings.isNullOrEmpty(contextTraceId) + ? UUID.randomUUID().toString().replaceAll("-", "") : contextTraceId; + } + + private String getRequestBody(WrappedHttpServletRequest request) throws IOException { + // 获取请求参数 + return request.getRequestParams(); + } + + @Trace(operationName = "HttpTraceLogFilter#getResponseBody") + private String getResponseBody(HttpServletResponse response) { + String responseBody = null; + ContentCachingResponseWrapper wrapper = WebUtils + .getNativeResponse(response, ContentCachingResponseWrapper.class); + if (wrapper != null) { + responseBody = new String(wrapper.getContentAsByteArray(), StandardCharsets.UTF_8); + } + return responseBody; + } + + public Map getRequestHeader(HttpServletRequest request) { + Map ret = new HashMap<>(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + ret.put(headerName, request.getHeader(headerName)); + } + return ret; + } + + @Trace(operationName = "HttpTraceLogFilter#updateResponse") + private void updateResponse(HttpServletResponse response) throws IOException { + ContentCachingResponseWrapper responseWrapper = WebUtils + .getNativeResponse(response, ContentCachingResponseWrapper.class); + Objects.requireNonNull(responseWrapper).copyBodyToResponse(); + } + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + private static class HttpTraceLog { + + /** + * 路径 + */ + private String path; + /** + * 参数 + */ + @JSONField(jsonDirect = true) + private String parameter; + private String requestContentType; + private String responseContentType; + private String method; + private Long timeTaken; + private Integer status; + /** + * 业务Code + */ + private Integer responseCode;//业务返回码 + /** + * 响应参数 + */ + @JSONField(jsonDirect = true) + private String responseBody; + + @JSONField(jsonDirect = true) + private Map requestHeaders; + + public String getParameter() { + if (parameter == null) { + return parameter; + } else { + return parameter.replaceAll("\n", "").replaceAll("\t", "").replaceAll("\r", ""); + } + } + + public String getResponseBody() { + if (responseBody == null) { + return responseBody; + } else { + return responseBody.replaceAll("\n", "").replaceAll("\t", "").replaceAll("\r", ""); + } + } + + public void setResponseBody(String responseBody) { + if (StringUtils.isBlank(responseBody)) { + return; + } + this.responseBody = responseBody; + JSONObject responseJson = null; + try { + responseJson = JSONObject.parseObject(responseBody); + this.responseCode = responseJson.getInteger("code"); + } catch (JSONException e) { + log.debug("ResponseBody非JSON返回", e); + } + } + } +} \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/config/filter/WrappedHttpServletRequest.java b/riven-server/src/main/java/cn/axzo/riven/config/filter/WrappedHttpServletRequest.java new file mode 100644 index 0000000..dd267d7 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/config/filter/WrappedHttpServletRequest.java @@ -0,0 +1,91 @@ +package cn.axzo.riven.config.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +import org.apache.commons.io.IOUtils; + +/** + * @Author: liyong.tian + * @Date: 2022/12/6 14:56 + * @Description: + */ +public class WrappedHttpServletRequest extends HttpServletRequestWrapper { + + private byte[] bytes; + private WrappedServletInputStream wrappedServletInputStream; + + public WrappedHttpServletRequest(HttpServletRequest request) throws IOException { + super(request); + // 读取输入流里的请求参数,并保存到bytes里 + bytes = IOUtils.toByteArray(request.getInputStream()); + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); + this.wrappedServletInputStream = new WrappedServletInputStream(byteArrayInputStream); + // 很重要,把post参数重新写入请求流 + reWriteInputStream(); + } + + /** + * 把参数重新写进请求里 + */ + public void reWriteInputStream() { + wrappedServletInputStream + .setStream(new ByteArrayInputStream(bytes != null ? bytes : new byte[0])); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return wrappedServletInputStream; + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new InputStreamReader(wrappedServletInputStream)); + } + + /** + * 获取post参数,可以自己再转为相应格式 + */ + public String getRequestParams() throws IOException { + return new String(bytes, this.getCharacterEncoding()); + } + + private class WrappedServletInputStream extends ServletInputStream { + + private InputStream stream; + + public WrappedServletInputStream(InputStream stream) { + this.stream = stream; + } + + public void setStream(InputStream stream) { + this.stream = stream; + } + + @Override + public int read() throws IOException { + return stream.read(); + } + + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/consumer/package-info.java b/riven-server/src/main/java/cn/axzo/riven/consumer/package-info.java new file mode 100644 index 0000000..a185ed2 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/consumer/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.consumer; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/controller/HealthCheckController.java b/riven-server/src/main/java/cn/axzo/riven/controller/HealthCheckController.java new file mode 100644 index 0000000..61c9409 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/controller/HealthCheckController.java @@ -0,0 +1,22 @@ +package cn.axzo.riven.controller; + +import cn.axzo.framework.web.http.ApiResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @Author: liyong.tian + * @Date: 2022/11/25 18:00 + * @Description: 健康检查接口 + */ +@RestController +public class HealthCheckController { + + /** + * 探活 + */ + @GetMapping("/checkDeath") + public ApiResponse checkDeath() { + return ApiResponse.ok("ok"); + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/controller/app/package-info.java b/riven-server/src/main/java/cn/axzo/riven/controller/app/package-info.java new file mode 100644 index 0000000..502e05f --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/controller/app/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.controller.app; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/controller/web/UserController.java b/riven-server/src/main/java/cn/axzo/riven/controller/web/UserController.java new file mode 100644 index 0000000..cdcd8d5 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/controller/web/UserController.java @@ -0,0 +1,74 @@ +package cn.axzo.riven.controller.web; + +import cn.axzo.riven.service.user.UserService; +import cn.axzo.riven.service.dto.request.user.NewUserDTO; +import cn.axzo.riven.service.dto.request.user.UpdateUserDTO; +import cn.axzo.riven.service.dto.request.user.UserQO; +import cn.axzo.riven.service.dto.response.user.UserVO; +import cn.azxo.framework.common.model.CommonPageResponse; +import cn.azxo.framework.common.model.CommonResponse; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + * @Author: liyong.tian + * @Date: 2022/9/2 + * @Description: + */ +@Slf4j +@Api(tags = "web-用户信息接口") +@RequestMapping("/api/v1") +@RestController +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @ApiOperation(value = "创建用户") + @PostMapping("/users") + public CommonResponse createUser(@Valid @RequestBody NewUserDTO dto) { + log.info("REST request to save user : {}", dto); + // 校验入参 + dto.valid(); + UserVO result = userService.create(dto); + return CommonResponse.success(result); + } + + @ApiOperation(value = "修改用户") + @PutMapping("/users/{id}") + public CommonResponse updateUser(@ApiParam("用户ID") @PathVariable Long id, + @Valid @RequestBody UpdateUserDTO dto) { + log.info("REST request to update user : {}", dto); + // 校验入参 + dto.valid(); + UserVO result = userService.update(id, dto); + return CommonResponse.success(result); + } + + @ApiOperation("获取用户列表") + @GetMapping("/users") + public CommonResponse> getUsers(@Valid UserQO userQO) { + CommonPageResponse results = userService.queryByPage(userQO); + return CommonResponse.success(results); + } + + @ApiOperation("获取用户详情") + @GetMapping("/users/{id}") + public CommonResponse getUser(@ApiParam("用户ID") @PathVariable Long id) { + UserVO result = userService.getOne(id); + return CommonResponse.success(result); + } + + @ApiOperation("删除用户") + @DeleteMapping("/users/{id}") + public CommonResponse deleteUser(@ApiParam("用户ID") @PathVariable Long id) { + userService.delete(id); + return CommonResponse.success(); + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/controller/web/UserResource.java b/riven-server/src/main/java/cn/axzo/riven/controller/web/UserResource.java new file mode 100644 index 0000000..0a1cf00 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/controller/web/UserResource.java @@ -0,0 +1,84 @@ +package cn.axzo.riven.controller.web; + +import cn.axzo.framework.domain.page.PageQO; +import cn.axzo.framework.domain.page.PageResp; +import cn.axzo.framework.web.http.ApiResponse; +import cn.axzo.framework.web.http.ApiPageResponse; +import cn.axzo.riven.service.dto.request.user.NewUserDTO; +import cn.axzo.riven.service.dto.request.user.UpdateUserDTO; +import cn.axzo.riven.service.dto.request.user.UserQO1; +import cn.axzo.riven.service.dto.response.user.UserRefVO; +import cn.axzo.riven.service.dto.response.user.UserVO; +import cn.axzo.riven.service.user.UserService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +/** + * @Author: liyong.tian + * @Date: 2022/10/28 + * @Description: 新项目搭建推荐方式 + */ +@Slf4j +@Api(tags = "web-用户信息接口") +@RequestMapping("/api/v2") +@RestController +@RequiredArgsConstructor +public class UserResource { + + private final UserService userService; + + @ApiOperation(value = "创建用户") + @PostMapping("/users") + public ApiResponse createUser(@Valid @RequestBody NewUserDTO dto) { + log.info("REST request to save user : {}", dto); + // 校验入参 + dto.valid(); + UserVO result = userService.create(dto); + return ApiResponse.ok(result); + } + + @ApiOperation(value = "修改用户") + @PutMapping("/users/{id}") + public ApiResponse updateUser(@ApiParam("用户ID") @PathVariable Long id, + @Valid @RequestBody UpdateUserDTO dto) { + log.info("REST request to update user : {}", dto); + // 校验入参 + dto.valid(); + UserVO result = userService.update(id, dto); + return ApiResponse.ok(result); + } + + @ApiOperation("获取用户列表") + @GetMapping("/users") + public ApiPageResponse getUsers(@ModelAttribute UserQO1 userQo, PageQO page) { + PageResp results = userService.find(userQo, page); + return ApiPageResponse.ok(results); + } + + @ApiOperation("获取用户信息及地址列表") + @GetMapping("/users/adress") + public ApiPageResponse getUserAddress(@ModelAttribute UserQO1 userQo, PageQO page) { + PageResp results = userService.queryUserAddress(userQo, page); + return ApiPageResponse.ok(results); + } + + @ApiOperation("获取用户详情") + @GetMapping("/users/{id}") + public ApiResponse getUser(@ApiParam("用户ID") @PathVariable Long id) { + UserVO result = userService.getOne(id); + return ApiResponse.ok(result); + } + + @ApiOperation("删除用户") + @DeleteMapping("/users/{id}") + public ApiResponse deleteUser(@ApiParam("用户ID") @PathVariable Long id) { + userService.delete(id); + return ApiResponse.ok(); + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/job/package-info.java b/riven-server/src/main/java/cn/axzo/riven/job/package-info.java new file mode 100644 index 0000000..008c944 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/job/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.job; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/UserDao.java b/riven-server/src/main/java/cn/axzo/riven/repository/UserDao.java new file mode 100644 index 0000000..b55cfad --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/UserDao.java @@ -0,0 +1,78 @@ +package cn.axzo.riven.repository; + +import cn.axzo.framework.domain.page.PageQO; +import cn.axzo.riven.repository.entity.address.AddressDO; +import cn.axzo.riven.repository.entity.user.UserDO; +import cn.axzo.riven.repository.entity.user.UserRefDO; +import cn.axzo.riven.repository.mapper.UserMapper; +import cn.axzo.riven.repository.mapper.UserReferenceMapper; +import cn.axzo.riven.service.dto.request.user.UserQO; +import cn.axzo.riven.service.dto.request.user.UserQO1; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.github.yulichang.toolkit.MPJWrappers; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Repository; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +@Repository +@RequiredArgsConstructor +public class UserDao extends ServiceImpl { + + private final UserMapper userMapper; + + private final UserReferenceMapper userReferenceMapper; + + public UserDO findById(Long id) { + return userMapper.selectById(id); + } + + public void delete(Long id) { + userMapper.deleteById(id); + } + + public IPage queryByPage(UserQO userQO) { + return userMapper.selectPage(userQO.toPage(), + Wrappers.lambdaQuery(UserDO.class) + .eq(userQO.getId() != null, UserDO::getId, userQO.getId()) + .like(StringUtils.isNotBlank(userQO.getName()), UserDO::getName, userQO.getName()) + .like(StringUtils.isNotBlank(userQO.getPhone()), UserDO::getPhone, userQO.getPhone()) + .like(StringUtils.isNotBlank(userQO.getEmail()), UserDO::getEmail, userQO.getEmail()) + .orderByDesc(UserDO::getCreateAt) + ); + } + + public IPage find(UserQO1 userQO, PageQO page) { + return userMapper.selectPage(page.toPage(), + Wrappers.lambdaQuery(UserDO.class) + .eq(userQO.getId() != null, UserDO::getId, userQO.getId()) + .like(StringUtils.isNotBlank(userQO.getName()), UserDO::getName, userQO.getName()) + .like(StringUtils.isNotBlank(userQO.getPhone()), UserDO::getPhone, userQO.getPhone()) + .like(StringUtils.isNotBlank(userQO.getEmail()), UserDO::getEmail, userQO.getEmail()) + .orderByDesc(UserDO::getCreateAt) + ); + } + + public IPage findUserAddress(UserQO1 userQO, PageQO page) { + MPJLambdaWrapper wrapper = MPJWrappers.lambdaJoin() + .selectAll(UserDO.class) + .select(AddressDO::getProvince) + .select(AddressDO::getCity) + .select(AddressDO::getDistrict) + .select(AddressDO::getStreet) + .leftJoin(AddressDO.class, AddressDO::getUserId, UserDO::getId) + .eq(userQO.getId() != null, UserDO::getId, userQO.getId()) + .like(StringUtils.isNotBlank(userQO.getName()), UserDO::getName, userQO.getName()) + .like(StringUtils.isNotBlank(userQO.getPhone()), UserDO::getPhone, userQO.getPhone()) + .like(StringUtils.isNotBlank(userQO.getEmail()), UserDO::getEmail, userQO.getEmail()) + .orderByDesc(UserDO::getCreateAt); + return userReferenceMapper.selectJoinPage(page.toPage(), UserRefDO.class, wrapper); + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/entity/address/AddressDO.java b/riven-server/src/main/java/cn/axzo/riven/repository/entity/address/AddressDO.java new file mode 100644 index 0000000..1cfffda --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/entity/address/AddressDO.java @@ -0,0 +1,25 @@ +package cn.axzo.riven.repository.entity.address; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @Author: liyong.tian + * @Date: 2023/8/4 16:35 + * @Description: + */ +@Data +@TableName("b_address") +public class AddressDO extends BaseEntity { + + private String province; + + private String city; + + private String district; + + private String street; + + private Long userId; +} diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/entity/package-info.java b/riven-server/src/main/java/cn/axzo/riven/repository/entity/package-info.java new file mode 100644 index 0000000..5f76091 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/entity/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.repository.entity; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/User.java b/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/User.java new file mode 100644 index 0000000..d617988 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/User.java @@ -0,0 +1,27 @@ +package cn.axzo.riven.repository.entity.user; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +@Data +@TableName("b_user") +public class User extends BaseEntity { + + private String name; + + private Integer sex; + + private Integer age; + + private String phone; + + private String email; + + private String address; +} diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/UserDO.java b/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/UserDO.java new file mode 100644 index 0000000..9be8685 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/UserDO.java @@ -0,0 +1,27 @@ +package cn.axzo.riven.repository.entity.user; + +import cn.axzo.framework.data.mybatisplus.model.BaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +@Data +@TableName("b_user") +public class UserDO extends BaseEntity { + + private String name; + + private Integer sex; + + private Integer age; + + private String phone; + + private String email; + + private String address; +} diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/UserRefDO.java b/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/UserRefDO.java new file mode 100644 index 0000000..6e1993b --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/entity/user/UserRefDO.java @@ -0,0 +1,32 @@ +package cn.axzo.riven.repository.entity.user; + +import lombok.Data; + +/** + * @Author: liyong.tian + * @Date: 2023/8/4 16:40 + * @Description: + */ +@Data +public class UserRefDO { + + private Long id; + + private String name; + + private Integer sex; + + private Integer age; + + private String phone; + + private String email; + + private String province; + + private String city; + + private String district; + + private String street; +} diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/mapper/AddressMapper.java b/riven-server/src/main/java/cn/axzo/riven/repository/mapper/AddressMapper.java new file mode 100644 index 0000000..5491d21 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/mapper/AddressMapper.java @@ -0,0 +1,14 @@ +package cn.axzo.riven.repository.mapper; + +import cn.axzo.riven.repository.entity.address.AddressDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * @Author: liyong.tian + * @Date: 2023/8/8 10:33 + * @Description: + */ +@Mapper +public interface AddressMapper extends BaseMapper { +} diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/mapper/UserMapper.java b/riven-server/src/main/java/cn/axzo/riven/repository/mapper/UserMapper.java new file mode 100644 index 0000000..f7137c1 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package cn.axzo.riven.repository.mapper; + +import cn.axzo.riven.repository.entity.user.UserDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +@Mapper +public interface UserMapper extends BaseMapper { +} diff --git a/riven-server/src/main/java/cn/axzo/riven/repository/mapper/UserReferenceMapper.java b/riven-server/src/main/java/cn/axzo/riven/repository/mapper/UserReferenceMapper.java new file mode 100644 index 0000000..8af4523 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/repository/mapper/UserReferenceMapper.java @@ -0,0 +1,14 @@ +package cn.axzo.riven.repository.mapper; + +import cn.axzo.riven.repository.entity.user.UserDO; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * @Author: liyong.tian + * @Date: 2023/8/4 16:49 + * @Description: + */ +@Mapper +public interface UserReferenceMapper extends MPJBaseMapper { +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/client/package-info.java b/riven-server/src/main/java/cn/axzo/riven/service/client/package-info.java new file mode 100644 index 0000000..9ad58df --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/client/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.service.client; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/service/converter/EntityConverter.java b/riven-server/src/main/java/cn/axzo/riven/service/converter/EntityConverter.java new file mode 100644 index 0000000..61feb33 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/converter/EntityConverter.java @@ -0,0 +1,15 @@ +package cn.axzo.riven.service.converter; + +import java.util.List; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +public interface EntityConverter{ + + V toVo(E var); + + List toVo(List var); +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/converter/UserConverter.java b/riven-server/src/main/java/cn/axzo/riven/service/converter/UserConverter.java new file mode 100644 index 0000000..2a9f990 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/converter/UserConverter.java @@ -0,0 +1,26 @@ +package cn.axzo.riven.service.converter; + +import cn.axzo.riven.service.dto.request.user.NewUserDTO; +import cn.axzo.riven.service.dto.request.user.UpdateUserDTO; +import cn.axzo.riven.service.dto.response.user.UserVO; +import cn.axzo.riven.repository.entity.user.UserDO; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +/** + * @Author: liyong.tian + * @Date: 2022/9/2 + * @Description: + */ +@Mapper( + componentModel = "spring", + nullValueCheckStrategy = ALWAYS +) +public interface UserConverter extends EntityConverter { + + UserDO toEntity(NewUserDTO dto); + + void updateEntity(UpdateUserDTO dto, @MappingTarget UserDO user); +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/converter/UserRefConverter.java b/riven-server/src/main/java/cn/axzo/riven/service/converter/UserRefConverter.java new file mode 100644 index 0000000..261a42c --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/converter/UserRefConverter.java @@ -0,0 +1,19 @@ +package cn.axzo.riven.service.converter; + +import cn.axzo.riven.repository.entity.user.UserRefDO; +import cn.axzo.riven.service.dto.response.user.UserRefVO; +import org.mapstruct.Mapper; + +import static org.mapstruct.NullValueCheckStrategy.ALWAYS; + +/** + * @Author: liyong.tian + * @Date: 2023/8/4 17:02 + * @Description: + */ +@Mapper( + componentModel = "spring", + nullValueCheckStrategy = ALWAYS +) +public interface UserRefConverter extends EntityConverter { +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/dto/request/package-info.java b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/package-info.java new file mode 100644 index 0000000..2599316 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.service.dto.request; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/NewUserDTO.java b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/NewUserDTO.java new file mode 100644 index 0000000..18294af --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/NewUserDTO.java @@ -0,0 +1,41 @@ +package cn.axzo.riven.service.dto.request.user; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * @Author: liyong.tian + * @Date: 2022/9/2 + * @Description: + */ +@Data +public class NewUserDTO { + + @ApiModelProperty(value = "名称", position = 1) + @NotBlank(message = "名称不能为空") + private String name; + + @ApiModelProperty(value = "性别", position = 2) + @NotNull + private Integer sex; + + @ApiModelProperty(value = "年龄", position = 3) + private Integer age; + + @ApiModelProperty(value = "电话", position = 4) + private String phone; + + @ApiModelProperty(value = "邮箱", position = 5) + private String email; + + public void valid() { + // 电话和邮箱不能都为空 + if (StringUtils.isEmpty(phone) && StringUtils.isEmpty(email)) { + throw new RuntimeException("电话和邮箱不能都为空"); + } + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UpdateUserDTO.java b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UpdateUserDTO.java new file mode 100644 index 0000000..8e47994 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UpdateUserDTO.java @@ -0,0 +1,44 @@ +package cn.axzo.riven.service.dto.request.user; + +import cn.axzo.framework.domain.web.ApiException; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import static cn.axzo.riven.common.enums.ErrorCode.USER_PHONE_EMAIL_IS_NULL; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +@Data +public class UpdateUserDTO { + + @ApiModelProperty(value = "名称", position = 1) + @NotBlank(message = "名称不能为空") + private String name; + + @ApiModelProperty(value = "性别", position = 2) + @NotNull + private Integer sex; + + @ApiModelProperty(value = "年龄", position = 3) + private Integer age; + + @ApiModelProperty(value = "电话", position = 4) + private String phone; + + @ApiModelProperty(value = "邮箱", position = 5) + private String email; + + public void valid() { + // 电话和邮箱不能都为空 + if (StringUtils.isEmpty(phone) && StringUtils.isEmpty(email)) { + throw new ApiException(USER_PHONE_EMAIL_IS_NULL); + } + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UserQO.java b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UserQO.java new file mode 100644 index 0000000..da9156f --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UserQO.java @@ -0,0 +1,26 @@ +package cn.axzo.riven.service.dto.request.user; + +import cn.axzo.framework.domain.page.PageQO; +import io.swagger.annotations.ApiParam; +import lombok.Data; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +@Data +public class UserQO extends PageQO { + + @ApiParam("主键") + private Long id; + + @ApiParam("姓名") + private String name; + + @ApiParam("手机") + private String phone; + + @ApiParam("邮箱") + private String email; +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UserQO1.java b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UserQO1.java new file mode 100644 index 0000000..1d77719 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/dto/request/user/UserQO1.java @@ -0,0 +1,25 @@ +package cn.axzo.riven.service.dto.request.user; + +import io.swagger.annotations.ApiParam; +import lombok.Data; + +/** + * @Author: liyong.tian + * @Date: 2022/9/5 + * @Description: + */ +@Data +public class UserQO1 { + + @ApiParam("主键") + private Long id; + + @ApiParam("姓名") + private String name; + + @ApiParam("手机") + private String phone; + + @ApiParam("邮箱") + private String email; +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/dto/response/package-info.java b/riven-server/src/main/java/cn/axzo/riven/service/dto/response/package-info.java new file mode 100644 index 0000000..c165e03 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/dto/response/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.service.dto.response; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/service/dto/response/user/UserRefVO.java b/riven-server/src/main/java/cn/axzo/riven/service/dto/response/user/UserRefVO.java new file mode 100644 index 0000000..3983842 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/dto/response/user/UserRefVO.java @@ -0,0 +1,43 @@ +package cn.axzo.riven.service.dto.response.user; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @Author: liyong.tian + * @Date: 2023/8/4 16:54 + * @Description: + */ +@Data +public class UserRefVO { + + @ApiModelProperty(value = "id", position = 1) + private Long id; + + @ApiModelProperty(value = "名称", position = 2) + private String name; + + @ApiModelProperty(value = "性别", position = 3) + private Integer sex; + + @ApiModelProperty(value = "年龄", position = 4) + private Integer age; + + @ApiModelProperty(value = "电话", position = 5) + private String phone; + + @ApiModelProperty(value = "邮箱", position = 6) + private String email; + + @ApiModelProperty(value = "省份", position = 7) + private String province; + + @ApiModelProperty(value = "城市", position = 8) + private String city; + + @ApiModelProperty(value = "区域", position = 9) + private String district; + + @ApiModelProperty(value = "街道", position = 10) + private String street; +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/dto/response/user/UserVO.java b/riven-server/src/main/java/cn/axzo/riven/service/dto/response/user/UserVO.java new file mode 100644 index 0000000..b4acd0f --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/dto/response/user/UserVO.java @@ -0,0 +1,31 @@ +package cn.axzo.riven.service.dto.response.user; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @Author: liyong.tian + * @Date: 2022/9/2 + * @Description: + */ +@Data +public class UserVO { + + @ApiModelProperty(value = "id", position = 1) + private Long id; + + @ApiModelProperty(value = "名称", position = 2) + private String name; + + @ApiModelProperty(value = "性别", position = 3) + private Integer sex; + + @ApiModelProperty(value = "年龄", position = 4) + private Integer age; + + @ApiModelProperty(value = "电话", position = 5) + private String phone; + + @ApiModelProperty(value = "邮箱", position = 6) + private String email; +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/event/package-info.java b/riven-server/src/main/java/cn/axzo/riven/service/event/package-info.java new file mode 100644 index 0000000..54bbd45 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/event/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.service.event; \ No newline at end of file diff --git a/riven-server/src/main/java/cn/axzo/riven/service/user/UserService.java b/riven-server/src/main/java/cn/axzo/riven/service/user/UserService.java new file mode 100644 index 0000000..9923168 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/user/UserService.java @@ -0,0 +1,32 @@ +package cn.axzo.riven.service.user; + +import cn.axzo.framework.domain.page.PageQO; +import cn.axzo.framework.domain.page.PageResp; +import cn.axzo.riven.service.dto.request.user.NewUserDTO; +import cn.axzo.riven.service.dto.request.user.UpdateUserDTO; +import cn.axzo.riven.service.dto.request.user.UserQO; +import cn.axzo.riven.service.dto.request.user.UserQO1; +import cn.axzo.riven.service.dto.response.user.UserRefVO; +import cn.axzo.riven.service.dto.response.user.UserVO; +import cn.azxo.framework.common.model.CommonPageResponse; + +/** + * @Author: liyong.tian + * @Date: 2022/9/2 + * @Description: + */ +public interface UserService { + UserVO create(NewUserDTO dto); + + UserVO update(Long id, UpdateUserDTO dto); + + UserVO getOne(Long id); + + void delete(Long id); + + CommonPageResponse queryByPage(UserQO userQO); + + PageResp find(UserQO1 userQo, PageQO page); + + PageResp queryUserAddress(UserQO1 userQo, PageQO page); +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/user/impl/UserServiceImpl.java b/riven-server/src/main/java/cn/axzo/riven/service/user/impl/UserServiceImpl.java new file mode 100644 index 0000000..8bdfc72 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/user/impl/UserServiceImpl.java @@ -0,0 +1,94 @@ +package cn.axzo.riven.service.user.impl; + +import cn.axzo.framework.domain.data.AssertUtil; +import cn.axzo.framework.domain.page.PageQO; +import cn.axzo.framework.domain.page.PageResp; +import cn.axzo.riven.common.enums.ErrorCode; +import cn.axzo.riven.repository.entity.user.UserDO; +import cn.axzo.riven.repository.entity.user.UserRefDO; +import cn.axzo.riven.service.converter.UserRefConverter; +import cn.axzo.riven.service.dto.request.user.NewUserDTO; +import cn.axzo.riven.service.dto.request.user.UpdateUserDTO; +import cn.axzo.riven.service.dto.request.user.UserQO; +import cn.axzo.riven.service.dto.request.user.UserQO1; +import cn.axzo.riven.service.dto.response.user.UserRefVO; +import cn.axzo.riven.service.dto.response.user.UserVO; +import cn.axzo.riven.repository.UserDao; +import cn.axzo.riven.service.user.UserService; +import cn.axzo.riven.service.converter.UserConverter; +import cn.azxo.framework.common.model.CommonPageResponse; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @Author: liyong.tian + * @Date: 2022/9/2 + * @Description: + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + private final UserConverter userConverter; + + private final UserRefConverter userRefConverter; + + private final UserDao userDao; + + @Override + public UserVO create(NewUserDTO dto) { + UserDO user = userConverter.toEntity(dto); + userDao.save(user); + return userConverter.toVo(user); + } + + @Override + public UserVO update(Long id, UpdateUserDTO dto) { + UserDO user = userDao.findById(id); + AssertUtil.notNull(user, ErrorCode.USER_NOT_EXISTS, id); + /*if (user == null) { + throw new ApiException(ErrorCode.USER_NOT_EXISTS, id); + }*/ + userConverter.updateEntity(dto, user); + return userConverter.toVo(user); + } + + @Override + public UserVO getOne(Long id) { + UserDO user = userDao.findById(id); + return userConverter.toVo(user); + } + + @Override + public void delete(Long id) { + userDao.delete(id); + } + + @Override + public CommonPageResponse queryByPage(UserQO userQo) { + IPage page = userDao.queryByPage(userQo); + List userList = page.getRecords(); + if (CollectionUtils.isEmpty(userList)) { + return CommonPageResponse.zero(userQo.getPage(), userQo.getPageSize()); + } + return new CommonPageResponse<>(page.getCurrent(), page.getSize(), page.getTotal(), userConverter.toVo(page.getRecords())); + } + + @Override + public PageResp find(UserQO1 userQo, PageQO page) { + IPage userPage = userDao.find(userQo, page); + return PageResp.list(userPage, userConverter.toVo(userPage.getRecords())); + } + + @Override + public PageResp queryUserAddress(UserQO1 userQo, PageQO page) { + IPage userRefPage = userDao.findUserAddress(userQo, page); + return PageResp.list(userRefPage, userRefConverter.toVo(userRefPage.getRecords())); + } +} diff --git a/riven-server/src/main/java/cn/axzo/riven/service/validator/package-info.java b/riven-server/src/main/java/cn/axzo/riven/service/validator/package-info.java new file mode 100644 index 0000000..8c1d645 --- /dev/null +++ b/riven-server/src/main/java/cn/axzo/riven/service/validator/package-info.java @@ -0,0 +1 @@ +package cn.axzo.riven.service.validator; \ No newline at end of file diff --git a/riven-server/src/main/resources/bootstrap.yml b/riven-server/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..ea7785b --- /dev/null +++ b/riven-server/src/main/resources/bootstrap.yml @@ -0,0 +1,44 @@ +spring: + application: + name: riven + cloud: + nacos: + config: + server-addr: ${NACOS_HOST:test-nacos.axzo.cn}:${NACOS_PORT:80} + file-extension: yaml + namespace: ${NACOS_NAMESPACE_ID:f82179f1-81a9-41a1-a489-4f9ab5660a6e} + prefix: ${spring.application.name} + profiles: + active: ${NACOS_PROFILES_ACTIVE:test} + main: + allow-bean-definition-overriding: true + +logging: + level: + com.alibaba.nacos.client.config.impl: WARN + +management: + endpoint: + metrics: + enabled: true + prometheus: + enabled: true + endpoints: + web: + exposure: + include: prometheus + path-mapping: + prometheus: metrics + base-path: / + metrics: + tags: + application: ${spring.application.name} + export: + prometheus: + enabled: true + server: + port: 8081 + +resp: + error-code: + non-base-mapping: {"[*]": OK} diff --git a/riven-server/src/main/resources/logback-spring.xml b/riven-server/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..922ff24 --- /dev/null +++ b/riven-server/src/main/resources/logback-spring.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/riven-server/src/test/java/cn/axzo/maven/archetype/server/AppTest.java b/riven-server/src/test/java/cn/axzo/maven/archetype/server/AppTest.java new file mode 100644 index 0000000..f5b5319 --- /dev/null +++ b/riven-server/src/test/java/cn/axzo/maven/archetype/server/AppTest.java @@ -0,0 +1,14 @@ +package cn.axzo.maven.archetype.server; + +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * Unit test for simple App. + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class AppTest { + +}