From bc55ba65451ab5aabf8a050a0c0e4287b84954f2 Mon Sep 17 00:00:00 2001 From: Luke0125 <94302726+BlackBear2003@users.noreply.github.com> Date: Sat, 28 Oct 2023 22:38:39 +0800 Subject: [PATCH] feat: Add apollo audit log common solution backend (#4985) * 7.3 * plc success except jpa part * successfully run by EntityScan annotation(not a good way) * successfully run by add packages to bean registrar * 'app create' audit log and query poc success * App create/update/delete poc success. * add License * add License * try to pass tests * try to pass tests * fix ${revision} to ${project.version} * add no-op implement and no-op autoconfiguration. add ApolloAuditLogApi for manually logging needs. add ApolloAuditLogDataInfluenceTableId (maybe multi-primary-key in the future). add ApolloAuditController by @Bean registered * rebase and add license * add audit controller * - change controller - add @DomainEvents to BaseEntity.java - change the way that data influences append - add annotations and codes to make app audited - change some classes' name - make api to query and record api - add event and listener to catch data influences * - make ApolloAuditHttpInterceptor uses Api while not tracer - try to reduce the affection of audit module on other modules * - retry workflows * - change for code review * - basically finish front-end part. * - fix for hound * - update * - update * - add ApolloAuditLogApiNoOpImpl.class to @ContextConfiguration at AppServiceTest.class * - add license and fix for hound * - fix front-end bug - add index for 2 new table * - fix front-end bug * - remove query for auditing AppNamespace * fix for changed requested * - change: always record if the method have no annotation - fix: little few bugs * - add test cover manually add dataInfluence when delete app - delete filter which is no longer needed * - try to write part of tests in impl - add Exception * - finish ApolloAuditLogApiJpaImplTest - add license * - finish most part of context's unit-tests * - finish some unit-tests * - fix database design bug - remove exception which would cause unexpected transaction-rollback * - add prompt sheet for disabling audit log feature - add API for get isEnabled of audit log feature - fix sql problem and add it to delta - remove 2 classes * - fix test bugs * - change the way of getting op-type for data influences * - enhance front-end design * - enhance front-end design * - add AuditLog Search dropdown * - add any-match data influences for batch-delete operation auditing * - fix bugs * - fix front-end bugs - add javadoc - Update ApolloAuditController to return ApolloAuditProperties directly * - fix test failure * - fix test failure * - update javadoc - add README.md * - update README * - update README --- apollo-adminservice/pom.xml | 4 + .../src/main/resources/application.properties | 4 + .../apollo/assembly/ApolloApplication.java | 3 +- .../src/main/resources/application.properties | 4 + apollo-audit/README.md | 147 +++++++++ apollo-audit/apollo-audit-annotation/pom.xml | 31 ++ .../audit/annotation/ApolloAuditLog.java | 69 ++++ .../ApolloAuditLogDataInfluence.java | 45 +++ .../ApolloAuditLogDataInfluenceTable.java | 63 ++++ ...ApolloAuditLogDataInfluenceTableField.java | 65 ++++ .../apollo/audit/annotation/OpType.java | 27 ++ apollo-audit/apollo-audit-api/pom.xml | 38 +++ .../apollo/audit/api/ApolloAuditLogApi.java | 27 ++ .../audit/api/ApolloAuditLogQueryApi.java | 98 ++++++ .../audit/api/ApolloAuditLogRecordApi.java | 85 +++++ .../apollo/audit/dto/ApolloAuditLogDTO.java | 113 +++++++ .../dto/ApolloAuditLogDataInfluenceDTO.java | 95 ++++++ .../audit/dto/ApolloAuditLogDetailsDTO.java | 54 +++ .../ApolloAuditLogDataInfluenceEvent.java | 44 +++ apollo-audit/apollo-audit-impl/pom.xml | 59 ++++ .../apollo/audit/ApolloAuditProperties.java | 33 ++ .../apollo/audit/ApolloAuditRegistrar.java | 32 ++ .../audit/aop/ApolloAuditSpanAspect.java | 93 ++++++ .../component/ApolloAuditHttpInterceptor.java | 43 +++ .../component/ApolloAuditLogApiJpaImpl.java | 155 +++++++++ .../component/ApolloAuditLogApiNoOpImpl.java | 77 +++++ .../audit/constants/ApolloAuditConstants.java | 27 ++ .../audit/context/ApolloAuditScope.java | 54 +++ .../context/ApolloAuditScopeManager.java | 48 +++ .../apollo/audit/context/ApolloAuditSpan.java | 112 +++++++ .../audit/context/ApolloAuditSpanContext.java | 79 +++++ .../context/ApolloAuditTraceContext.java | 58 ++++ .../audit/context/ApolloAuditTracer.java | 189 +++++++++++ .../controller/ApolloAuditController.java | 97 ++++++ .../apollo/audit/entity/ApolloAuditLog.java | 177 ++++++++++ .../entity/ApolloAuditLogDataInfluence.java | 151 +++++++++ .../apollo/audit/entity/BaseEntity.java | 136 ++++++++ ...lloAuditLogDataInfluenceEventListener.java | 37 +++ ...ApolloAuditLogDataInfluenceRepository.java | 35 ++ .../repository/ApolloAuditLogRepository.java | 38 +++ .../ApolloAuditLogDataInfluenceService.java | 57 ++++ .../audit/service/ApolloAuditLogService.java | 89 +++++ .../ApolloAuditLogQueryApiPreAuthorizer.java | 23 ++ .../spi/ApolloAuditOperatorSupplier.java | 23 ++ ...oAuditLogQueryApiDefaultPreAuthorizer.java | 28 ++ .../ApolloAuditOperatorDefaultSupplier.java | 41 +++ .../apollo/audit/util/ApolloAuditUtil.java | 122 +++++++ .../apollo/audit/MockBeanFactory.java | 101 ++++++ .../apollo/audit/MockDataInfluenceEntity.java | 65 ++++ .../ApolloAuditHttpInterceptorTest.java | 76 +++++ .../ApolloAuditLogApiJpaImplTest.java | 309 ++++++++++++++++++ .../ApolloAuditScopeManagerTest.java | 48 +++ .../context/ApolloAuditTraceContextTest.java | 120 +++++++ .../audit/context/ApolloAuditTracerTest.java | 251 ++++++++++++++ .../controller/ApolloAuditControllerTest.java | 159 +++++++++ .../apollo-audit-spring-boot-starter/pom.xml | 47 +++ .../ApolloAuditAutoConfiguration.java | 117 +++++++ .../ApolloAuditNoOpAutoConfiguration.java | 82 +++++ .../main/resources/META-INF/spring.factories | 3 + apollo-audit/pom.xml | 42 +++ apollo-biz/pom.xml | 9 + .../apollo/biz/service/AppService.java | 9 +- .../DatabaseDiscoveryIntegrationTest.java | 4 - ...coveryWithoutDecoratorIntegrationTest.java | 3 - ...ClearApplicationRunnerIntegrationTest.java | 2 - apollo-common/pom.xml | 4 + .../framework/apollo/common/entity/App.java | 5 + .../apollo/common/entity/AppNamespace.java | 7 + .../apollo/common/entity/BaseEntity.java | 9 + .../src/main/resources/application.properties | 2 +- apollo-portal/pom.xml | 4 + .../apollo/portal/api/AdminServiceAPI.java | 5 + ...loAuditLogQueryApiPortalPreAuthorizer.java | 38 +++ .../ApolloAuditOperatorPortalSupplier.java | 38 +++ .../portal/component/RestTemplateFactory.java | 6 +- .../portal/controller/AppController.java | 5 + .../portal/service/AppNamespaceService.java | 16 +- .../apollo/portal/service/AppService.java | 16 +- .../src/main/resources/application.properties | 4 + .../main/resources/static/audit_log_menu.html | 168 ++++++++++ .../static/audit_log_trace_detail.html | 237 ++++++++++++++ .../src/main/resources/static/i18n/en.json | 37 ++- .../src/main/resources/static/i18n/zh-CN.json | 37 ++- .../src/main/resources/static/scripts/app.js | 6 +- .../controller/AuditLogMenuController.js | 176 ++++++++++ .../AuditLogTraceDetailController.js | 151 +++++++++ .../scripts/services/AuditLogService.js | 134 ++++++++ .../resources/static/styles/audit-log.css | 140 ++++++++ .../resources/static/views/common/nav.html | 1 + .../apollo/portal/service/AppServiceTest.java | 38 ++- pom.xml | 24 +- scripts/sql/apolloconfigdb.sql | 49 +++ scripts/sql/apolloportaldb.sql | 49 +++ .../v210-v220/apolloconfigdb-v210-v220.sql | 50 ++- .../v210-v220/apolloportaldb-v210-v220.sql | 50 ++- 95 files changed, 6057 insertions(+), 25 deletions(-) create mode 100644 apollo-audit/README.md create mode 100644 apollo-audit/apollo-audit-annotation/pom.xml create mode 100644 apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLog.java create mode 100644 apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluence.java create mode 100644 apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluenceTable.java create mode 100644 apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluenceTableField.java create mode 100644 apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/OpType.java create mode 100644 apollo-audit/apollo-audit-api/pom.xml create mode 100644 apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogApi.java create mode 100644 apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogQueryApi.java create mode 100644 apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogRecordApi.java create mode 100644 apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDTO.java create mode 100644 apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDataInfluenceDTO.java create mode 100644 apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDetailsDTO.java create mode 100644 apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/event/ApolloAuditLogDataInfluenceEvent.java create mode 100644 apollo-audit/apollo-audit-impl/pom.xml create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/ApolloAuditProperties.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/ApolloAuditRegistrar.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/aop/ApolloAuditSpanAspect.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditHttpInterceptor.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImpl.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiNoOpImpl.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/constants/ApolloAuditConstants.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditScope.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditScopeManager.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditSpan.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditSpanContext.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTraceContext.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTracer.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/controller/ApolloAuditController.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/ApolloAuditLog.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/ApolloAuditLogDataInfluence.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/BaseEntity.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/listener/ApolloAuditLogDataInfluenceEventListener.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/repository/ApolloAuditLogDataInfluenceRepository.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/repository/ApolloAuditLogRepository.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/service/ApolloAuditLogDataInfluenceService.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/service/ApolloAuditLogService.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/ApolloAuditLogQueryApiPreAuthorizer.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/ApolloAuditOperatorSupplier.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/defaultimpl/ApolloAuditLogQueryApiDefaultPreAuthorizer.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/defaultimpl/ApolloAuditOperatorDefaultSupplier.java create mode 100644 apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/util/ApolloAuditUtil.java create mode 100644 apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/MockBeanFactory.java create mode 100644 apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/MockDataInfluenceEntity.java create mode 100644 apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditHttpInterceptorTest.java create mode 100644 apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImplTest.java create mode 100644 apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditScopeManagerTest.java create mode 100644 apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTraceContextTest.java create mode 100644 apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTracerTest.java create mode 100644 apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/controller/ApolloAuditControllerTest.java create mode 100644 apollo-audit/apollo-audit-spring-boot-starter/pom.xml create mode 100644 apollo-audit/apollo-audit-spring-boot-starter/src/main/java/com/ctrip/framework/apollo/audit/configuration/ApolloAuditAutoConfiguration.java create mode 100644 apollo-audit/apollo-audit-spring-boot-starter/src/main/java/com/ctrip/framework/apollo/audit/configuration/ApolloAuditNoOpAutoConfiguration.java create mode 100644 apollo-audit/apollo-audit-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 apollo-audit/pom.xml create mode 100644 apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/audit/ApolloAuditLogQueryApiPortalPreAuthorizer.java create mode 100644 apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/audit/ApolloAuditOperatorPortalSupplier.java create mode 100644 apollo-portal/src/main/resources/static/audit_log_menu.html create mode 100644 apollo-portal/src/main/resources/static/audit_log_trace_detail.html create mode 100644 apollo-portal/src/main/resources/static/scripts/controller/AuditLogMenuController.js create mode 100644 apollo-portal/src/main/resources/static/scripts/controller/AuditLogTraceDetailController.js create mode 100644 apollo-portal/src/main/resources/static/scripts/services/AuditLogService.js create mode 100644 apollo-portal/src/main/resources/static/styles/audit-log.css diff --git a/apollo-adminservice/pom.xml b/apollo-adminservice/pom.xml index 6e9b29b48b4..a3f83935d1b 100644 --- a/apollo-adminservice/pom.xml +++ b/apollo-adminservice/pom.xml @@ -35,6 +35,10 @@ com.ctrip.framework.apollo apollo-biz + + com.ctrip.framework.apollo + apollo-audit-spring-boot-starter + org.springframework.cloud diff --git a/apollo-adminservice/src/main/resources/application.properties b/apollo-adminservice/src/main/resources/application.properties index 5ede28e5782..aa1bbde74bc 100644 --- a/apollo-adminservice/src/main/resources/application.properties +++ b/apollo-adminservice/src/main/resources/application.properties @@ -22,3 +22,7 @@ # You may change the following config to activate different database profiles like h2/postgres spring.profiles.group.github = mysql + +# true: enabled the new feature of audit log +# false/missing: disable it +apollo.audit.log.enabled = true diff --git a/apollo-assembly/src/main/java/com/ctrip/framework/apollo/assembly/ApolloApplication.java b/apollo-assembly/src/main/java/com/ctrip/framework/apollo/assembly/ApolloApplication.java index 16b8e086048..3de97cfe142 100644 --- a/apollo-assembly/src/main/java/com/ctrip/framework/apollo/assembly/ApolloApplication.java +++ b/apollo-assembly/src/main/java/com/ctrip/framework/apollo/assembly/ApolloApplication.java @@ -17,6 +17,7 @@ package com.ctrip.framework.apollo.assembly; import com.ctrip.framework.apollo.adminservice.AdminServiceApplication; +import com.ctrip.framework.apollo.audit.configuration.ApolloAuditAutoConfiguration; import com.ctrip.framework.apollo.configservice.ConfigServiceApplication; import com.ctrip.framework.apollo.portal.PortalApplication; @@ -31,7 +32,7 @@ import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, - HibernateJpaAutoConfiguration.class}) + HibernateJpaAutoConfiguration.class, ApolloAuditAutoConfiguration.class}) public class ApolloApplication { private static final Logger logger = LoggerFactory.getLogger(ApolloApplication.class); diff --git a/apollo-assembly/src/main/resources/application.properties b/apollo-assembly/src/main/resources/application.properties index 5ede28e5782..0ed1257cba8 100644 --- a/apollo-assembly/src/main/resources/application.properties +++ b/apollo-assembly/src/main/resources/application.properties @@ -22,3 +22,7 @@ # You may change the following config to activate different database profiles like h2/postgres spring.profiles.group.github = mysql + +# true: enabled the new feature of audit log +# false/missing: disable it +apollo.audit.log.enabled = true \ No newline at end of file diff --git a/apollo-audit/README.md b/apollo-audit/README.md new file mode 100644 index 00000000000..e328839c6ee --- /dev/null +++ b/apollo-audit/README.md @@ -0,0 +1,147 @@ +# Features: Apollo-Audit-Log + +This module provides audit log functions for other Apollo modules. + +Only apolloconfig's developer need to read it, + +apolloconfig's user doesn't need. + +## How to enable/disable + +We can switch this module freely by properties: + +by adding properties to application.properties: + +``` +# true: enabled the new feature of audit log +# false/missing: disable it +apollo.audit.log.enabled = true +``` + +## How to generate audit log + +### Append an AuditLog + +Through an AuditLog, we have the ability to record **Who, When, Why, Where, What and How** operates something. + +We can do this by using annotations: + +```java +@ApolloAuditLog(type=OpType.CREATE,name="App.create") +public App create() { + // ... +} +``` + +Through this, an AuditLog will be created and its AuditScope will be activated during the execution of this method. + +Equally, we can use ApolloAuditLogApi to do this manually: + +```java +public App create() { + try(AutoCloseable auditScope = api.appendAuditLog(type, name)) { + // ... + } +} +/**************OR**************/ +public App create() { + Autocloseable auditScope = api.appendAuditLog(type, name); + // ... + auditScope.close(); +} +``` + +The only thing you need to pay attention to is that you need to close this scope manually~ + +### Append DataInfluence + +This function can also be implemented automatically and manually. + +There is a corresponding relationship between DataInfluences and a certain AuditLog, and they are caused by this AuditLog. But not all AuditLogs will generate DataInfluences! + +#### Mark which data change + +First, we need to add audit-bean-definition to class of the entity you want to audit: + +```java +@ApolloAuditLogDataInfluenceTable(tableName = "App") +public class App extends BaseEntity { + @ApolloAuditLogDataInfluenceTableField(fieldName = "Name") + private String name; + private String orgId; +} +``` + +In class App, we define that its data-influence table' name is "App", the field "name" needs to be audited and its audit field name in the table "App" is "Name". The field "orgId" is no need to be audited. + +Second, use API's method to append it: + +Actually we don't need to manually call it. We can depend on the DomainEvents that pre-set in BaseEntity: + +```java +@DomainEvents +public Collection domainEvents() { + return Collections.singletonList(new ApolloAuditLogDataInfluenceEvent(this.getClass(), this)); +} +``` + +And this will call appendDataInfluences automatically by the listener. + +#### Manually + +```java +/** + * Append DataInfluences by a list of entities needs to be audited, and their + * audit-bean-definition. + */ +ApolloAuditLogApi.appendDataInfluences(List entities, Class beanDefinition); +``` + +Just call the api method in an active scope, the data influences will combine with the log automatically. + +```java +public App create() { + try(AutoCloseable auditScope = api.appendAuditLog(type, name)) { + // ... + api.appendDataInfluences(appList, App.class); + // or. + api.appendDataInfluence("App","10001","Name","xxx"); + } +} +``` + +#### some tricky situations + +Yet, sometimes we can't catch the domain events like some operations that directly change database fields. We can use annotations to catch the input parameters: + +```java +@ApolloAuditLog(type=OpType.DELETE,name="AppNamespace.batchDeleteByAppId") +public AppNamespace batchDeleteByAppId( + @ApolloAuditLogDataInfluence + @ApolloAuditLogDataInfluenceTable(tableName="AppNamespace") + @ApolloAuditLogDataInfluenceTableField(fieldName="AppId") String appId) { + // ... +} +``` + +This will generate a special data influence. It means that all entities matching the input parameter value have been affected. + +## How to verify the audit-log work + +### check-by-UI + +The entrance of audit log UI is in Admin Tools. + +Then, we can check if the AuditLogs are created properly by searching or just find in table below. + +Then, check in the trace detail page. + +We can check if the relationship between AuditLogs are correct and the DataInfluences caused by certain AuditLog is logically established. + +In the rightmost column, we can view the historical operation records of the specified field's value. Null means being deleted~ + +### check-by-database + +The databases are in ApolloPortalDB, the table `AuditLog` and `AuditLogDataInfluence`. + +We can verify if the parent/followsfrom relationships are in line with our expectations. \ No newline at end of file diff --git a/apollo-audit/apollo-audit-annotation/pom.xml b/apollo-audit/apollo-audit-annotation/pom.xml new file mode 100644 index 00000000000..f56bd98e4fb --- /dev/null +++ b/apollo-audit/apollo-audit-annotation/pom.xml @@ -0,0 +1,31 @@ + + + + + apollo-audit + com.ctrip.framework.apollo + ${revision} + + 4.0.0 + + apollo-audit-annotation + ${revision} + + \ No newline at end of file diff --git a/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLog.java b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLog.java new file mode 100644 index 00000000000..047a85eaf1d --- /dev/null +++ b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLog.java @@ -0,0 +1,69 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mark which method should be audited, add to controller or service's method. + *

+ * Define the attributes of the operation for persisting and querying. When adding to controller's + * methods, suggested that don't set name, and it will automatically be set to request's url. + *

+ * Example usage: + *
+ * {@code
+ * @ApolloAuditLog(type=OpType.CREATE,name="App.create")
+ * public App create() {
+ *   // ...
+ * }
+ * }
+ * 
+ * + * @author luke0125 + * @since 2.2.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApolloAuditLog { + + /** + * Define the type of operation. + * + * @return operation type + */ + OpType type(); + + /** + * Define the name of operation. The requested URL will be taken by default if no specific name is + * specified. + * + * @return operation name + */ + String name() default ""; + + /** + * Define the description of operation. Default is "no description". + * + * @return operation description + */ + String description() default "no description"; +} + diff --git a/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluence.java b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluence.java new file mode 100644 index 00000000000..1481c109b58 --- /dev/null +++ b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluence.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Combine with {@link ApolloAuditLog}, mark which method's parameter is audit log's data change. + *

+ * Example usage: + *
+ * {@code
+ * @ApolloAuditLog(type=OpType.DELETE,name="AppNamespace.batchDeleteByAppId")
+ * public AppNamespace batchDeleteByAppId(
+ *            @ApolloAuditLogDataInfluence String appId) {
+ *   // ...
+ * }
+ * }
+ * 
+ * + * @author luke0125 + * @since 2.2.0 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApolloAuditLogDataInfluence { + +} diff --git a/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluenceTable.java b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluenceTable.java new file mode 100644 index 00000000000..32ad68d93af --- /dev/null +++ b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluenceTable.java @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mainly used in class definitions, indicates the name of the corresponding audit data table of + * this class. + *

+ * It could also be used on method parameters to express the table name of the class which this + * parameter belongs to. + *

+ * Example usage: + *
+ * {@code
+ * CASE 1:
+ * @ApolloAuditLogDataInfluenceTable(tableName="App")
+ * public class App {
+ *   // ...
+ * }
+ * CASE 2:
+ * @ApolloAuditLog(type=OpType.DELETE,name="AppNamespace.batchDeleteByAppId")
+ * public AppNamespace batchDeleteByAppId(
+ *   @ApolloAuditLogDataInfluence
+ *   @ApolloAuditLogDataInfluenceTable(tableName="AppNamespace") String appId) {
+ *   // ...
+ * }
+ * }
+ * 
+ * + * @author luke0125 + * @since 2.2.0 + */ +@Target({ElementType.TYPE, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApolloAuditLogDataInfluenceTable { + + /** + * Define the table name(entity name) of audited entity. + * + * @return table name + */ + String tableName(); + +} diff --git a/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluenceTableField.java b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluenceTableField.java new file mode 100644 index 00000000000..5e7d4921df9 --- /dev/null +++ b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/ApolloAuditLogDataInfluenceTableField.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mainly used in field definitions, indicates the name of the corresponding audit data table field + * of this member variables(attributes). + *

+ * It could also be used on method parameters to express the field name of the field which this + * parameter matches. + *

+ * Example usage: + *
+ * {@code
+ * CASE 1:
+ * public class App {
+ *   @ApolloAuditLogDataInfluenceTableField(fieldName="AppId")
+ *   private String appId;
+ *   // ...
+ * }
+ * CASE 2:
+ * @ApolloAuditLog(type=OpType.DELETE,name="AppNamespace.batchDeleteByAppId")
+ * public AppNamespace batchDeleteByAppId(
+ *   @ApolloAuditLogDataInfluence
+ *   @ApolloAuditLogDataInfluenceTable(tableName="AppNamespace")
+ *   @ApolloAuditLogDataInfluenceTableField(fieldName="AppId") String appId) {
+ *   // ...
+ * }
+ * }
+ * 
+ * + * @author luke0125 + * @since 2.2.0 + */ +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApolloAuditLogDataInfluenceTableField { + + /** + * Define the field name of audited entity field. + * + * @return field name + */ + String fieldName(); + +} diff --git a/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/OpType.java b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/OpType.java new file mode 100644 index 00000000000..f1284f27254 --- /dev/null +++ b/apollo-audit/apollo-audit-annotation/src/main/java/com/ctrip/framework/apollo/audit/annotation/OpType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.annotation; + +/** + * Includes all types of audit operations. + * + * @author luke0125 + * @since 2.2.0 + */ +public enum OpType { + CREATE, UPDATE, DELETE, RPC +} diff --git a/apollo-audit/apollo-audit-api/pom.xml b/apollo-audit/apollo-audit-api/pom.xml new file mode 100644 index 00000000000..d6756b90a93 --- /dev/null +++ b/apollo-audit/apollo-audit-api/pom.xml @@ -0,0 +1,38 @@ + + + + + apollo-audit + com.ctrip.framework.apollo + ${revision} + + 4.0.0 + + apollo-audit-api + ${revision} + + + + com.ctrip.framework.apollo + apollo-audit-annotation + + + + \ No newline at end of file diff --git a/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogApi.java b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogApi.java new file mode 100644 index 00000000000..44dca09d9a8 --- /dev/null +++ b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogApi.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.api; + +/** + * API interface that integrates all functional interfaces. + * + * @author luke0125 + * @since 2.2.0 + */ +public interface ApolloAuditLogApi extends ApolloAuditLogRecordApi, ApolloAuditLogQueryApi{ + +} diff --git a/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogQueryApi.java b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogQueryApi.java new file mode 100644 index 00000000000..6483c4a18bf --- /dev/null +++ b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogQueryApi.java @@ -0,0 +1,98 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.api; + +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDetailsDTO; +import java.util.Date; +import java.util.List; + +/** + * Mainly used to query AuditLogs and DataInfluences. + * + * @author luke0125 + * @since 2.2.0 + */ +public interface ApolloAuditLogQueryApi { + + /** + * Query all AuditLogs by page + * + * @param page index from 0 + * @param size size of a page + * @return List of ApolloAuditLogDTO + */ + List queryLogs(int page, int size); + + /** + * Query AuditLogs by operator name and time limit and page + * + * @param opName operation name of querying + * @param startDate expect result after or equal this time + * @param endDate expect result before or equal this time + * @param page index from 0 + * @param size size of a page + * @return List of ApolloAuditLogDTO + */ + List queryLogsByOpName(String opName, Date startDate, Date endDate, int page, + int size); + + /** + * Query AuditLogDetails by trace id. + *

+ * An AuditLogDetail contains an AuditLog and DataInfluences it caused. + *

+ *
+   * {@code
+   *   An AuditLogDetail:
+   *   {
+   *     LogDTO:{},
+   *     DataInfluencesDTO:[]
+   *   }
+   * }
+   * 
+ * + * @param traceId unique id of a operation trace + * @return List of ApolloAuditLogDetailsDTO + */ + List queryTraceDetails(String traceId); + + /** + * Query DataInfluences by specific entity's specified field and page + * + * @param entityName target entity's name(audit table name) + * @param entityId target entity's id(audit table id) + * @param fieldName target field's name(audit field id) + * @param page index from 0 + * @param size size of a page + * @return List of ApolloAuditLogDetailsDTO + */ + List queryDataInfluencesByField(String entityName, + String entityId, String fieldName, int page, int size); + + /** + * Fuzzy search related AuditLog by query-string and page, page index from 0. + * + * @param query input query string, used to fuzzy search + * @param page index from 0 + * @param size size of a page + * @return List of ApolloAuditLogDetailsDTO + */ + List searchLogByNameOrTypeOrOperator(String query, int page, int size); + +} \ No newline at end of file diff --git a/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogRecordApi.java b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogRecordApi.java new file mode 100644 index 00000000000..829038d16e6 --- /dev/null +++ b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/api/ApolloAuditLogRecordApi.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.api; + +import com.ctrip.framework.apollo.audit.annotation.OpType; +import java.util.List; + +/** + * Mainly used to Record AuditLogs and DataInfluences. + * + * @author luke0125 + * @since 2.2.0 + */ +public interface ApolloAuditLogRecordApi { + + /** + * Append a new AuditLog by type and name.The operation's description would be default by "no + * description". + *

+ * Functionally aligned with annotations. + *

+ * Need to close the audited scope manually! + * + * @param type operation's type + * @param name operation's name + * @return Returns an AuditScope needs to be closed when the audited operation ends. + */ + AutoCloseable appendAuditLog(OpType type, String name); + + /** + * Append a new AuditLog by type and name and description. + *

+ * Functionally aligned with annotations. + *

+ * Need to close the audited scope manually! + * + * @param type operation's type + * @param name operation's name + * @param description operation's description + * @return Returns an AuditScope needs to be closed when the audited operation ends. + */ + AutoCloseable appendAuditLog(OpType type, String name, String description); + + /** + * Directly append a new DataInfluence by the attributes it should have. + *

+ * Only when there is an active AuditScope in the context at this time can appending DataInfluence + * be performed correctly. It will be considered to be caused by currently active operations. + * + * @param entityName influenced entity's name (audit table name) + * @param entityId influenced entity's id (audit table id) + * @param fieldName influenced entity's field name (audit table field) + * @param fieldCurrentValue influenced entity's field current value + */ + void appendDataInfluence(String entityName, String entityId, String fieldName, + String fieldCurrentValue); + + /** + * Append DataInfluences by a list of entities needs to be audited, and their + * audit-bean-definition. + *

+ * Only when there is an active AuditScope in the context at this time can appending + * DataInfluences be performed correctly. They will be considered to be caused by currently active + * operations. + * + * @param entities entities needs to be audited + * @param beanDefinition entities' audit-bean-definition + */ + void appendDataInfluences(List entities, Class beanDefinition); + +} diff --git a/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDTO.java b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDTO.java new file mode 100644 index 00000000000..d8df7760ac7 --- /dev/null +++ b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDTO.java @@ -0,0 +1,113 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.dto; + +import java.util.Date; + +public class ApolloAuditLogDTO { + + private long id; + private String traceId; + private String spanId; + private String parentSpanId; + private String followsFromSpanId; + private String operator; + private String opType; + private String opName; + private String description; + private Date happenedTime; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + + public String getSpanId() { + return spanId; + } + + public void setSpanId(String spanId) { + this.spanId = spanId; + } + + public String getParentSpanId() { + return parentSpanId; + } + + public void setParentSpanId(String parentSpanId) { + this.parentSpanId = parentSpanId; + } + + public String getFollowsFromSpanId() { + return followsFromSpanId; + } + + public void setFollowsFromSpanId(String followsFromSpanId) { + this.followsFromSpanId = followsFromSpanId; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getOpType() { + return opType; + } + + public void setOpType(String opType) { + this.opType = opType; + } + + public String getOpName() { + return opName; + } + + public void setOpName(String opName) { + this.opName = opName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getHappenedTime() { + return happenedTime; + } + + public void setHappenedTime(Date happenedTime) { + this.happenedTime = happenedTime; + } +} diff --git a/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDataInfluenceDTO.java b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDataInfluenceDTO.java new file mode 100644 index 00000000000..b8ef95a9113 --- /dev/null +++ b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDataInfluenceDTO.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.dto; + +import java.util.Date; + +public class ApolloAuditLogDataInfluenceDTO { + + private long id; + private String spanId; + private String influenceEntityName; + private String influenceEntityId; + private String fieldName; + private String fieldOldValue; + private String fieldNewValue; + private Date happenedTime; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getSpanId() { + return spanId; + } + + public void setSpanId(String spanId) { + this.spanId = spanId; + } + + public String getInfluenceEntityName() { + return influenceEntityName; + } + + public void setInfluenceEntityName(String influenceEntityName) { + this.influenceEntityName = influenceEntityName; + } + + public String getInfluenceEntityId() { + return influenceEntityId; + } + + public void setInfluenceEntityId(String influenceEntityId) { + this.influenceEntityId = influenceEntityId; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getFieldOldValue() { + return fieldOldValue; + } + + public void setFieldOldValue(String fieldOldValue) { + this.fieldOldValue = fieldOldValue; + } + + public String getFieldNewValue() { + return fieldNewValue; + } + + public void setFieldNewValue(String fieldNewValue) { + this.fieldNewValue = fieldNewValue; + } + + public Date getHappenedTime() { + return happenedTime; + } + + public void setHappenedTime(Date happenedTime) { + this.happenedTime = happenedTime; + } +} diff --git a/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDetailsDTO.java b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDetailsDTO.java new file mode 100644 index 00000000000..a4abecb994c --- /dev/null +++ b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/dto/ApolloAuditLogDetailsDTO.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.dto; + +import java.util.List; + +/** + * A combine of a log and its data influences + */ +public class ApolloAuditLogDetailsDTO { + + private ApolloAuditLogDTO logDTO; + private List dataInfluenceDTOList; + + public ApolloAuditLogDetailsDTO(ApolloAuditLogDTO logDTO, + List dataInfluenceDTOList) { + this.logDTO = logDTO; + this.dataInfluenceDTOList = dataInfluenceDTOList; + } + + public ApolloAuditLogDetailsDTO() { + } + + public ApolloAuditLogDTO getLogDTO() { + return logDTO; + } + + public void setLogDTO(ApolloAuditLogDTO logDTO) { + this.logDTO = logDTO; + } + + public List getDataInfluenceDTOList() { + return dataInfluenceDTOList; + } + + public void setDataInfluenceDTOList( + List dataInfluenceDTOList) { + this.dataInfluenceDTOList = dataInfluenceDTOList; + } +} diff --git a/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/event/ApolloAuditLogDataInfluenceEvent.java b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/event/ApolloAuditLogDataInfluenceEvent.java new file mode 100644 index 00000000000..7c81c7a047f --- /dev/null +++ b/apollo-audit/apollo-audit-api/src/main/java/com/ctrip/framework/apollo/audit/event/ApolloAuditLogDataInfluenceEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.event; + +public class ApolloAuditLogDataInfluenceEvent { + + private Class beanDefinition; + private Object entity; + + public ApolloAuditLogDataInfluenceEvent(Class beanDefinition, Object entity) { + this.beanDefinition = beanDefinition; + this.entity = entity; + } + + public Class getBeanDefinition() { + return beanDefinition; + } + + public void setBeanDefinition(Class beanDefinition) { + this.beanDefinition = beanDefinition; + } + + public Object getEntity() { + return entity; + } + + public void setEntity(Object entity) { + this.entity = entity; + } +} diff --git a/apollo-audit/apollo-audit-impl/pom.xml b/apollo-audit/apollo-audit-impl/pom.xml new file mode 100644 index 00000000000..4c2246bf1c9 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/pom.xml @@ -0,0 +1,59 @@ + + + + + apollo-audit + com.ctrip.framework.apollo + ${revision} + + 4.0.0 + + apollo-audit-impl + ${revision} + + + + com.ctrip.framework.apollo + apollo-audit-annotation + + + + com.ctrip.framework.apollo + apollo-audit-api + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.security + spring-security-core + + + + + \ No newline at end of file diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/ApolloAuditProperties.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/ApolloAuditProperties.java new file mode 100644 index 00000000000..070d266047e --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/ApolloAuditProperties.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "apollo.audit.log") +public class ApolloAuditProperties { + + private boolean enabled = false; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/ApolloAuditRegistrar.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/ApolloAuditRegistrar.java new file mode 100644 index 00000000000..5c6d515bdfc --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/ApolloAuditRegistrar.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit; + +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +public class ApolloAuditRegistrar implements ImportBeanDefinitionRegistrar { + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + AutoConfigurationPackages.register(registry, "com.ctrip.framework.apollo.audit.entity", + "com.ctrip.framework.apollo.audit.repository"); + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/aop/ApolloAuditSpanAspect.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/aop/ApolloAuditSpanAspect.java new file mode 100644 index 00000000000..535a07dfda6 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/aop/ApolloAuditSpanAspect.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.aop; + +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluence; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTable; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTableField; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +@Aspect +public class ApolloAuditSpanAspect { + + private final ApolloAuditLogApi api; + + public ApolloAuditSpanAspect(ApolloAuditLogApi api) { + this.api = api; + } + + @Pointcut("@annotation(auditLog)") + public void setAuditSpan(ApolloAuditLog auditLog) { + } + + @Around(value = "setAuditSpan(auditLog)") + public Object around(ProceedingJoinPoint pjp, ApolloAuditLog auditLog) throws Throwable { + String opName = auditLog.name(); + if (opName.equals("") && RequestContextHolder.getRequestAttributes() != null) { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + opName = servletRequestAttributes.getRequest().getRequestURI(); + } + + try (AutoCloseable scope = api.appendAuditLog(auditLog.type(), opName, + auditLog.description())) { + Object[] args = pjp.getArgs(); + Method method = findMethod(pjp.getTarget().getClass(), pjp.getSignature().getName()); + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + Annotation[] annotations = method.getParameterAnnotations()[i]; + if (Arrays.stream(annotations).anyMatch(anno -> anno instanceof ApolloAuditLogDataInfluence)) { + String entityName = null; + String fieldName = null; + for(int j = 0; j < annotations.length; j++) { + if(annotations[j] instanceof ApolloAuditLogDataInfluenceTable) { + entityName = ((ApolloAuditLogDataInfluenceTable) annotations[j]).tableName(); + } + if(annotations[j] instanceof ApolloAuditLogDataInfluenceTableField) { + fieldName = ((ApolloAuditLogDataInfluenceTableField) annotations[j]).fieldName(); + } + } + if (entityName != null && fieldName != null) { + String matchedValue = String.valueOf(arg); + api.appendDataInfluence("AnyMatched", entityName, fieldName, matchedValue); + } + } + } + return pjp.proceed(); + } + } + + Method findMethod(Class clazz, String methodName) { + for (Method method : clazz.getDeclaredMethods()) { + if (method.getName().equals(methodName)) { + return method; + } + } + return null; + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditHttpInterceptor.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditHttpInterceptor.java new file mode 100644 index 00000000000..582219996e6 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditHttpInterceptor.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.component; + +import com.ctrip.framework.apollo.audit.context.ApolloAuditTraceContext; +import java.io.IOException; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +public class ApolloAuditHttpInterceptor implements ClientHttpRequestInterceptor { + + private final ApolloAuditTraceContext traceContext; + + public ApolloAuditHttpInterceptor(ApolloAuditTraceContext traceContext) { + this.traceContext = traceContext; + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + if (traceContext.tracer() != null) { + request = traceContext.tracer().inject(request); + } + ClientHttpResponse response = execution.execute(request, body); + return response; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImpl.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImpl.java new file mode 100644 index 00000000000..d7c1baab2a4 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImpl.java @@ -0,0 +1,155 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.component; + +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTableField; +import com.ctrip.framework.apollo.audit.annotation.OpType; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; +import com.ctrip.framework.apollo.audit.context.ApolloAuditScope; +import com.ctrip.framework.apollo.audit.context.ApolloAuditTraceContext; +import com.ctrip.framework.apollo.audit.context.ApolloAuditTracer; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDetailsDTO; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLogDataInfluence; +import com.ctrip.framework.apollo.audit.service.ApolloAuditLogDataInfluenceService; +import com.ctrip.framework.apollo.audit.service.ApolloAuditLogService; +import com.ctrip.framework.apollo.audit.util.ApolloAuditUtil; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +public class ApolloAuditLogApiJpaImpl implements ApolloAuditLogApi { + + private final ApolloAuditLogService logService; + private final ApolloAuditLogDataInfluenceService dataInfluenceService; + private final ApolloAuditTraceContext traceContext; + + public ApolloAuditLogApiJpaImpl(ApolloAuditLogService logService, + ApolloAuditLogDataInfluenceService dataInfluenceService, ApolloAuditTraceContext traceContext) { + this.logService = logService; + this.dataInfluenceService = dataInfluenceService; + this.traceContext = traceContext; + } + + @Override + public AutoCloseable appendAuditLog(OpType type, String name) { + return appendAuditLog(type, name, "no description"); + } + + @Override + public AutoCloseable appendAuditLog(OpType type, String name, String description) { + ApolloAuditTracer tracer = traceContext.tracer(); + if (Objects.isNull(tracer)) { + return () -> {}; + } + ApolloAuditScope scope = tracer.startActiveSpan(type, name, description); + logService.logSpan(scope.activeSpan()); + return scope; + } + + @Override + public void appendDataInfluence(String entityName, String entityId, String fieldName, + String fieldCurrentValue) { + // might be + if (traceContext.tracer() == null) { + return; + } + if (traceContext.tracer().getActiveSpan() == null) { + return; + } + String spanId = traceContext.tracer().getActiveSpan().spanId(); + OpType type = traceContext.tracer().getActiveSpan().getOpType(); + ApolloAuditLogDataInfluence.Builder builder = ApolloAuditLogDataInfluence.builder().spanId(spanId) + .entityName(entityName).entityId(entityId).fieldName(fieldName); + switch (type) { + case CREATE: + case UPDATE: + builder.newVal(fieldCurrentValue); + break; + case DELETE: + builder.oldVal(fieldCurrentValue); + } + dataInfluenceService.save(builder.build()); + } + + @Override + public void appendDataInfluences(List entities, Class beanDefinition) { + String tableName = ApolloAuditUtil.getApolloAuditLogTableName(beanDefinition); + if (Objects.isNull(tableName) || tableName.equals("")) { + return; + } + List dataInfluenceFields = ApolloAuditUtil.getAnnotatedFields( + ApolloAuditLogDataInfluenceTableField.class, beanDefinition); + Field idField = ApolloAuditUtil.getPersistenceIdFieldByAnnotation(beanDefinition); + entities.forEach(e -> { + try { + idField.setAccessible(true); + String tableId = idField.get(e).toString(); + for (Field f : dataInfluenceFields) { + f.setAccessible(true); + String val = String.valueOf(f.get(e)); + String fieldName = f.getAnnotation(ApolloAuditLogDataInfluenceTableField.class).fieldName(); + appendDataInfluence(tableId, tableName, fieldName, val); + } + } catch (IllegalAccessException ex) { + throw new IllegalArgumentException("failed append data influence, " + + "might due to wrong beanDefinition for entity audited", ex); + } + }); + } + + @Override + public List queryLogs(int page, int size) { + return ApolloAuditUtil.logListToDTOList(logService.findAll(page, size)); + } + + @Override + public List queryLogsByOpName(String opName, Date startDate, Date endDate, + int page, int size) { + if (startDate == null && endDate == null) { + return ApolloAuditUtil.logListToDTOList(logService.findByOpName(opName, page, size)); + } + return ApolloAuditUtil.logListToDTOList( + logService.findByOpNameAndTime(opName, startDate, endDate, page, size)); + } + + @Override + public List queryTraceDetails(String traceId) { + List detailsDTOList = new ArrayList<>(); + logService.findByTraceId(traceId).forEach(log -> { + detailsDTOList.add(new ApolloAuditLogDetailsDTO(ApolloAuditUtil.logToDTO(log), + ApolloAuditUtil.dataInfluenceListToDTOList( + dataInfluenceService.findBySpanId(log.getSpanId())))); + }); + return detailsDTOList; + } + + @Override + public List queryDataInfluencesByField(String entityName, + String entityId, String fieldName, int page, int size) { + return ApolloAuditUtil.dataInfluenceListToDTOList(dataInfluenceService.findByEntityNameAndEntityIdAndFieldName(entityName, entityId, + fieldName, page, size)); + } + + @Override + public List searchLogByNameOrTypeOrOperator(String query, int page, int size) { + return ApolloAuditUtil.logListToDTOList(logService.searchLogByNameOrTypeOrOperator(query, page, size)); + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiNoOpImpl.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiNoOpImpl.java new file mode 100644 index 00000000000..144937192b6 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiNoOpImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.component; + +import com.ctrip.framework.apollo.audit.annotation.OpType; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDetailsDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO; +import java.util.Date; +import java.util.List; + +public class ApolloAuditLogApiNoOpImpl implements ApolloAuditLogApi { + + //do nothing, for default impl + + @Override + public AutoCloseable appendAuditLog(OpType type, String name) { + return appendAuditLog(type, name, null); + } + + @Override + public AutoCloseable appendAuditLog(OpType type, String name, String description) { + return () -> { + }; + } + + @Override + public void appendDataInfluence(String entityName, String entityId, String fieldName, + String fieldCurrentValue) { + } + + @Override + public void appendDataInfluences(List entities, Class beanDefinition) { + } + + @Override + public List queryLogs(int page, int size) { + return null; + } + + @Override + public List queryLogsByOpName(String opName, Date startDate, + Date endDate, int page, int size) { + return null; + } + + @Override + public List queryTraceDetails(String traceId) { + return null; + } + + @Override + public List queryDataInfluencesByField(String entityName, + String entityId, String fieldName, int page, int size) { + return null; + } + + @Override + public List searchLogByNameOrTypeOrOperator(String query, int page, int size) { + return null; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/constants/ApolloAuditConstants.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/constants/ApolloAuditConstants.java new file mode 100644 index 00000000000..602dbd1bff1 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/constants/ApolloAuditConstants.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.constants; + +public interface ApolloAuditConstants { + String TRACE_ID = "Apollo-Audit-TraceId"; + String SPAN_ID = "Apollo-Audit-SpanId"; + String OPERATOR = "Apollo-Audit-Operator"; + String PARENT_ID = "Apollo-Audit-ParentId"; + String FOLLOWS_FROM_ID = "Apollo-Audit-FollowsFromId"; + + String TRACER = "Apollo-Audit-Tracer"; +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditScope.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditScope.java new file mode 100644 index 00000000000..6dc60a23c5a --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditScope.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.context; + +public class ApolloAuditScope implements AutoCloseable { + + private final ApolloAuditScopeManager manager; + + private ApolloAuditSpan activeSpan; + private ApolloAuditScope hangUp; + private String lastSpanId; + + public ApolloAuditScope(ApolloAuditSpan activeSpan, ApolloAuditScopeManager manager) { + this.hangUp = manager.getScope(); + this.activeSpan = activeSpan; + this.manager = manager; + this.lastSpanId = null; + } + + public ApolloAuditSpan activeSpan() { + return this.activeSpan; + } + + @Override + public void close(){ + // closing span become parent-scope's last span + if (hangUp != null) { + hangUp.setLastSpanId(activeSpan().spanId()); + } + this.manager.setScope(hangUp); + } + + public String getLastSpanId() { + return lastSpanId; + } + + public void setLastSpanId(String lastSpanId) { + this.lastSpanId = lastSpanId; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditScopeManager.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditScopeManager.java new file mode 100644 index 00000000000..f4c02f37062 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditScopeManager.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.context; + +import java.io.IOException; + +public class ApolloAuditScopeManager { + + private ApolloAuditScope scope; + + public ApolloAuditScopeManager() { + } + + public ApolloAuditScope activate(ApolloAuditSpan span) { + setScope(new ApolloAuditScope(span, this)); + return getScope(); + } + + public void deactivate() throws IOException { + getScope().close(); + } + + public ApolloAuditSpan activeSpan() { + return getScope() == null ? null : getScope().activeSpan(); + } + + public ApolloAuditScope getScope() { + return scope; + } + + public void setScope(ApolloAuditScope scope) { + this.scope = scope; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditSpan.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditSpan.java new file mode 100644 index 00000000000..6bf6bd0265e --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditSpan.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.context; + +import com.ctrip.framework.apollo.audit.annotation.OpType; +import java.util.Date; + +public class ApolloAuditSpan { + + private OpType opType; + private String opName; + private String description; + private Date startTime; + private Date endTime; + + private ApolloAuditSpanContext context; + + public ApolloAuditSpanContext context() { + return this.context; + } + + //just do nothing + public void finish() { + endTime = new Date(); + } + + public void log() { + } + + // sugar method + public String spanId() { + return context.getSpanId(); + } + + public String operator() { + return context.getOperator(); + } + + public String traceId() { + return context.getTraceId(); + } + + public String parentId() { + return context.getParentId(); + } + + public String followsFromId() { + return context.getFollowsFromId(); + } + + public OpType getOpType() { + return opType; + } + + public void setOpType(OpType opType) { + this.opType = opType; + } + + public String getOpName() { + return opName; + } + + public void setOpName(String opName) { + this.opName = opName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public ApolloAuditSpanContext getContext() { + return context; + } + + public void setContext(ApolloAuditSpanContext context) { + this.context = context; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } +} \ No newline at end of file diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditSpanContext.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditSpanContext.java new file mode 100644 index 00000000000..bb57ec914f4 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditSpanContext.java @@ -0,0 +1,79 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.context; + +public class ApolloAuditSpanContext { + + private String traceId; + private String spanId; + private String operator; + private String parentId; + private String followsFromId; + + public ApolloAuditSpanContext(String traceId, String spanId) { + this.traceId = traceId; + this.spanId = spanId; + } + + public ApolloAuditSpanContext(String traceId, String spanId, String operator, String parentId, String followsFromId) { + this.traceId = traceId; + this.spanId = spanId; + this.operator = operator; + this.parentId = parentId; + this.followsFromId = followsFromId; + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + + public String getSpanId() { + return spanId; + } + + public void setSpanId(String spanId) { + this.spanId = spanId; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public String getFollowsFromId() { + return followsFromId; + } + + public void setFollowsFromId(String followsFromId) { + this.followsFromId = followsFromId; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTraceContext.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTraceContext.java new file mode 100644 index 00000000000..9f8a46668a5 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTraceContext.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.context; + +import com.ctrip.framework.apollo.audit.constants.ApolloAuditConstants; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditOperatorSupplier; +import java.util.Objects; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +public class ApolloAuditTraceContext { + + private final ApolloAuditOperatorSupplier operatorSupplier; + + public ApolloAuditTraceContext(ApolloAuditOperatorSupplier operatorSupplier) { + this.operatorSupplier = operatorSupplier; + } + + // if not get one, create one and re-get it + public ApolloAuditTracer tracer() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + Object tracer = requestAttributes.getAttribute(ApolloAuditConstants.TRACER, + RequestAttributes.SCOPE_REQUEST); + if (tracer != null) { + return ((ApolloAuditTracer) tracer); + } else { + ApolloAuditTracer newTracer = new ApolloAuditTracer(new ApolloAuditScopeManager(), operatorSupplier); + setTracer(newTracer); + return newTracer; + } + } + return null; + } + + void setTracer(ApolloAuditTracer tracer) { + if (Objects.nonNull(RequestContextHolder.getRequestAttributes())) { + RequestContextHolder.getRequestAttributes() + .setAttribute(ApolloAuditConstants.TRACER, tracer, RequestAttributes.SCOPE_REQUEST); + } + } + + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTracer.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTracer.java new file mode 100644 index 00000000000..1b281196a8c --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTracer.java @@ -0,0 +1,189 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.context; + +import com.ctrip.framework.apollo.audit.annotation.OpType; +import com.ctrip.framework.apollo.audit.constants.ApolloAuditConstants; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditOperatorSupplier; +import com.ctrip.framework.apollo.audit.util.ApolloAuditUtil; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +public class ApolloAuditTracer { + + private final ApolloAuditScopeManager manager; + private final ApolloAuditOperatorSupplier operatorSupplier; + + public ApolloAuditTracer(ApolloAuditScopeManager manager, + ApolloAuditOperatorSupplier operatorSupplier) { + this.manager = manager; + this.operatorSupplier = operatorSupplier; + } + + public ApolloAuditScopeManager scopeManager() { + return manager; + } + + public HttpRequest inject(HttpRequest request) { + Map> map = new HashMap<>(); + if (manager.activeSpan() == null) { + return request; + } + map.put(ApolloAuditConstants.TRACE_ID, + Collections.singletonList(manager.activeSpan().traceId())); + map.put(ApolloAuditConstants.SPAN_ID, + Collections.singletonList(manager.activeSpan().spanId())); + map.put(ApolloAuditConstants.OPERATOR, + Collections.singletonList(manager.activeSpan().operator())); + map.put(ApolloAuditConstants.PARENT_ID, + Collections.singletonList(manager.activeSpan().parentId())); + map.put(ApolloAuditConstants.FOLLOWS_FROM_ID, + Collections.singletonList(manager.activeSpan().followsFromId())); + + HttpHeaders headers = request.getHeaders(); + headers.putAll(map); + + return request; + } + + public ApolloAuditSpan startSpan(OpType type, String name, String description) { + ApolloAuditSpan activeSpan = getActiveSpan(); + AuditSpanBuilder builder = new AuditSpanBuilder(type, name); + builder = builder.description(description); + builder = activeSpan == null ? builder.asRootSpan(operatorSupplier.getOperator()) + : builder.asChildOf(activeSpan); + String followsFromId = scopeManager().getScope() == null ? + null : scopeManager().getScope().getLastSpanId(); + builder = builder.followsFrom(followsFromId); + + return builder.build(); + } + + public ApolloAuditScope startActiveSpan(OpType type, String name, String description) { + ApolloAuditSpan startSpan = startSpan(type, name, description); + return activate(startSpan); + } + + public ApolloAuditScope activate(ApolloAuditSpan span) { + return scopeManager().activate(span); + } + + private ApolloAuditSpan getActiveSpanFromHttp() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (servletRequestAttributes == null) { + return null; + } + HttpServletRequest request = servletRequestAttributes.getRequest(); + String traceId = request.getHeader(ApolloAuditConstants.TRACE_ID); + String spanId = request.getHeader(ApolloAuditConstants.SPAN_ID); + String operator = request.getHeader(ApolloAuditConstants.OPERATOR); + String parentId = request.getHeader(ApolloAuditConstants.PARENT_ID); + String followsFromId = request.getHeader(ApolloAuditConstants.FOLLOWS_FROM_ID); + if (Objects.isNull(traceId)) { + return null; + } else { + ApolloAuditSpanContext context = new ApolloAuditSpanContext(traceId, spanId, operator, parentId, followsFromId); + return spanBuilder(null, null).regenerateByContext(context); + } + } + + private ApolloAuditSpan getActiveSpanFromContext() { + return scopeManager().activeSpan(); + } + + public ApolloAuditSpan getActiveSpan() { + ApolloAuditSpan activeSpan = getActiveSpanFromContext(); + if (activeSpan != null) { + return activeSpan; + } + activeSpan = getActiveSpanFromHttp(); + // might be null, root span generate should be done in other place + return activeSpan; + } + + public AuditSpanBuilder spanBuilder(OpType type, String name) { + return new AuditSpanBuilder(type, name); + } + + public static class AuditSpanBuilder { + + private final OpType opType; + private final String opName; + private String spanId; + private String traceId; + private String operator; + private String parentId; + private String followsFromId; + private String description; + + public AuditSpanBuilder(OpType type, String name) { + opType = type; + opName = name; + } + + public AuditSpanBuilder asChildOf(ApolloAuditSpan parent) { + traceId = parent.traceId(); + operator = parent.operator(); + parentId = parent.spanId(); + return this; + } + + public AuditSpanBuilder asRootSpan(String operator) { + traceId = ApolloAuditUtil.generateId(); + this.operator = operator; + return this; + } + + public AuditSpanBuilder followsFrom(String id) { + this.followsFromId = id; + return this; + } + + public AuditSpanBuilder description(String val) { + this.description = val; + return this; + } + + public ApolloAuditSpan regenerateByContext(ApolloAuditSpanContext val) { + ApolloAuditSpan span = new ApolloAuditSpan(); + span.setContext(val); + return span; + } + + public ApolloAuditSpan build() { + ApolloAuditSpan span = new ApolloAuditSpan(); + spanId = ApolloAuditUtil.generateId(); + ApolloAuditSpanContext context = new ApolloAuditSpanContext(traceId, spanId, operator, + parentId, followsFromId); + span.setContext(context); + span.setDescription(description); + span.setOpName(opName); + span.setOpType(opType); + span.setStartTime(new Date()); + return span; + } + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/controller/ApolloAuditController.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/controller/ApolloAuditController.java new file mode 100644 index 00000000000..2b6605f6905 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/controller/ApolloAuditController.java @@ -0,0 +1,97 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.controller; + +import com.ctrip.framework.apollo.audit.ApolloAuditProperties; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDetailsDTO; +import java.util.Date; +import java.util.List; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * About page: index from 0, default size is 10 + * + * @author luke + */ +@RestController +@RequestMapping("/apollo/audit") +public class ApolloAuditController { + + private final ApolloAuditLogApi api; + private final ApolloAuditProperties properties; + + public ApolloAuditController(ApolloAuditLogApi api, ApolloAuditProperties properties) { + this.api = api; + this.properties = properties; + } + + @GetMapping("/properties") + public ApolloAuditProperties getProperties() { + return properties; + } + + @GetMapping("/logs") + @PreAuthorize(value = "@apolloAuditLogQueryApiPreAuthorizer.hasQueryPermission()") + public List findAllAuditLogs(int page, int size) { + List logDTOList = api.queryLogs(page, size); + return logDTOList; + } + + @GetMapping("/trace") + @PreAuthorize(value = "@apolloAuditLogQueryApiPreAuthorizer.hasQueryPermission()") + public List findTraceDetails(@RequestParam String traceId) { + List detailsDTOList = api.queryTraceDetails(traceId); + return detailsDTOList; + } + + @GetMapping("/logs/opName") + @PreAuthorize(value = "@apolloAuditLogQueryApiPreAuthorizer.hasQueryPermission()") + public List findAllAuditLogsByOpNameAndTime(@RequestParam String opName, + @RequestParam int page, @RequestParam int size, + @RequestParam(value = "startDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.S") Date startDate, + @RequestParam(value = "endDate", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.S") Date endDate) { + List logDTOList = api.queryLogsByOpName(opName, startDate, endDate, page, + size); + return logDTOList; + } + + @GetMapping("/logs/dataInfluences/field") + @PreAuthorize(value = "@apolloAuditLogQueryApiPreAuthorizer.hasQueryPermission()") + public List findDataInfluencesByField( + @RequestParam String entityName, @RequestParam String entityId, + @RequestParam String fieldName, int page, int size) { + List dataInfluenceDTOList = api.queryDataInfluencesByField( + entityName, entityId, fieldName, page, size); + return dataInfluenceDTOList; + } + + @GetMapping("/logs/by-name-or-type-or-operator") + @PreAuthorize(value = "@apolloAuditLogQueryApiPreAuthorizer.hasQueryPermission()") + public List findAuditLogsByNameOrTypeOrOperator(@RequestParam String query, int page, int size) { + List logDTOList = api.searchLogByNameOrTypeOrOperator(query, page, size); + return logDTOList; + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/ApolloAuditLog.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/ApolloAuditLog.java new file mode 100644 index 00000000000..2cf31d3482c --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/ApolloAuditLog.java @@ -0,0 +1,177 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.entity; + +import java.util.Date; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name = "`AuditLog`") +public class ApolloAuditLog extends BaseEntity { + + @Column(name = "TraceId", nullable = false) + private String traceId; + + @Column(name = "SpanId", nullable = false) + private String spanId; + + @Column(name = "ParentSpanId", nullable = true) + private String parentSpanId; + + @Column(name = "FollowsFromSpanId", nullable = true) + private String followsFromSpanId; + + @Column(name = "Operator", nullable = true) + private String operator; + + @Column(name = "OpType", nullable = true) + private String opType; + + @Column(name = "OpName", nullable = true) + private String opName; + + @Column(name = "Description", nullable = true) + private String description; + + public static Builder builder() { + return new Builder(); + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + + public String getSpanId() { + return spanId; + } + + public void setSpanId(String spanId) { + this.spanId = spanId; + } + + public String getParentSpanId() { + return parentSpanId; + } + + public void setParentSpanId(String parentSpanId) { + this.parentSpanId = parentSpanId; + } + + public String getFollowsFromSpanId() { + return followsFromSpanId; + } + + public void setFollowsFromSpanId(String followsFromSpanId) { + this.followsFromSpanId = followsFromSpanId; + } + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getOpType() { + return opType; + } + + public void setOpType(String opType) { + this.opType = opType; + } + + public String getOpName() { + return opName; + } + + public void setOpName(String opName) { + this.opName = opName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public static class Builder { + + ApolloAuditLog auditLog = new ApolloAuditLog(); + + public Builder() { + } + + public Builder traceId(String val) { + auditLog.setTraceId(val); + return this; + } + + public Builder spanId(String val) { + auditLog.setSpanId(val); + return this; + } + + public Builder parentSpanId(String val) { + auditLog.setParentSpanId(val); + return this; + } + + public Builder followsFromSpanId(String val) { + auditLog.setFollowsFromSpanId(val); + return this; + } + + public Builder operator(String val) { + auditLog.setOperator(val); + return this; + } + + public Builder opType(String val) { + auditLog.setOpType(val); + return this; + } + + public Builder opName(String val) { + auditLog.setOpName(val); + return this; + } + + public Builder description(String val) { + auditLog.setDescription(val); + return this; + } + + public Builder happenedTime(Date val) { + auditLog.setDataChangeCreatedTime(val); + return this; + } + + public ApolloAuditLog build() { + return auditLog; + } + + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/ApolloAuditLogDataInfluence.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/ApolloAuditLogDataInfluence.java new file mode 100644 index 00000000000..bf4234b97fc --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/ApolloAuditLogDataInfluence.java @@ -0,0 +1,151 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name = "`AuditLogDataInfluence`") +public class ApolloAuditLogDataInfluence extends BaseEntity { + + @Column(name = "SpanId", nullable = false) + private String spanId; + + @Column(name = "InfluenceEntityName", nullable = false) + private String influenceEntityName; + + @Column(name = "InfluenceEntityId", nullable = false) + private String influenceEntityId; + + @Column(name = "FieldName") + private String fieldName; + + @Column(name = "FieldOldValue") + private String fieldOldValue; + + @Column(name = "FieldNewValue") + private String fieldNewValue; + + public ApolloAuditLogDataInfluence() { + } + + public ApolloAuditLogDataInfluence(String spanId, String entityName, String entityId, + String fieldName, String oldVal, String newVal) { + this.spanId = spanId; + this.influenceEntityName = entityName; + this.influenceEntityId = entityId; + this.fieldName = fieldName; + this.fieldOldValue = oldVal; + this.fieldNewValue = newVal; + } + + public static Builder builder() { + return new Builder(); + } + + public String getSpanId() { + return spanId; + } + + public void setSpanId(String spanId) { + this.spanId = spanId; + } + + public String getInfluenceEntityName() { + return influenceEntityName; + } + + public void setInfluenceEntityName(String influenceEntityName) { + this.influenceEntityName = influenceEntityName; + } + + public String getInfluenceEntityId() { + return influenceEntityId; + } + + public void setInfluenceEntityId(String influenceEntityId) { + this.influenceEntityId = influenceEntityId; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getFieldOldValue() { + return fieldOldValue; + } + + public void setFieldOldValue(String fieldOldValue) { + this.fieldOldValue = fieldOldValue; + } + + public String getFieldNewValue() { + return fieldNewValue; + } + + public void setFieldNewValue(String fieldNewValue) { + this.fieldNewValue = fieldNewValue; + } + + public static class Builder { + + ApolloAuditLogDataInfluence influence = new ApolloAuditLogDataInfluence(); + + public Builder() { + } + + public Builder spanId(String val) { + influence.setSpanId(val); + return this; + } + + public Builder entityId(String val) { + influence.setInfluenceEntityId(val); + return this; + } + + public Builder entityName(String val) { + influence.setInfluenceEntityName(val); + return this; + } + + public Builder fieldName(String val) { + influence.setFieldName(val); + return this; + } + + public Builder oldVal(String val) { + influence.setFieldOldValue(val); + return this; + } + + public Builder newVal(String val) { + influence.setFieldNewValue(val); + return this; + } + + public ApolloAuditLogDataInfluence build() { + return influence; + } + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/BaseEntity.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/BaseEntity.java new file mode 100644 index 00000000000..70a5754486c --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/entity/BaseEntity.java @@ -0,0 +1,136 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.entity; + +import java.util.Date; +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.MappedSuperclass; +import javax.persistence.PrePersist; +import javax.persistence.PreRemove; +import javax.persistence.PreUpdate; + +@MappedSuperclass +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +public abstract class BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "`Id`") + private long id; + + @Column(name = "`IsDeleted`", columnDefinition = "Bit default '0'") + protected boolean isDeleted = false; + + @Column(name = "`DeletedAt`", columnDefinition = "Bigint default '0'") + protected long deletedAt; + + @Column(name = "`DataChange_CreatedBy`") + private String dataChangeCreatedBy; + + @Column(name = "`DataChange_CreatedTime`") + private Date dataChangeCreatedTime; + + @Column(name = "`DataChange_LastModifiedBy`") + private String dataChangeLastModifiedBy; + + @Column(name = "`DataChange_LastTime`") + private Date dataChangeLastModifiedTime; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public boolean isDeleted() { + return isDeleted; + } + + public void setDeleted(boolean deleted) { + isDeleted = deleted; + if (deleted && this.deletedAt == 0) { + // also set deletedAt value as epoch millisecond + this.deletedAt = System.currentTimeMillis(); + } else if (!deleted) { + this.deletedAt = 0L; + } + } + + public long getDeletedAt() { + return deletedAt; + } + + public String getDataChangeCreatedBy() { + return dataChangeCreatedBy; + } + + public void setDataChangeCreatedBy(String dataChangeCreatedBy) { + this.dataChangeCreatedBy = dataChangeCreatedBy; + } + + public Date getDataChangeCreatedTime() { + return dataChangeCreatedTime; + } + + public void setDataChangeCreatedTime(Date dataChangeCreatedTime) { + this.dataChangeCreatedTime = dataChangeCreatedTime; + } + + public String getDataChangeLastModifiedBy() { + return dataChangeLastModifiedBy; + } + + public void setDataChangeLastModifiedBy(String dataChangeLastModifiedBy) { + this.dataChangeLastModifiedBy = dataChangeLastModifiedBy; + } + + public Date getDataChangeLastModifiedTime() { + return dataChangeLastModifiedTime; + } + + public void setDataChangeLastModifiedTime(Date dataChangeLastModifiedTime) { + this.dataChangeLastModifiedTime = dataChangeLastModifiedTime; + } + + @PrePersist + protected void prePersist() { + if (this.dataChangeCreatedTime == null) { + dataChangeCreatedTime = new Date(); + } + if (this.dataChangeLastModifiedTime == null) { + dataChangeLastModifiedTime = new Date(); + } + } + + @PreUpdate + protected void preUpdate() { + this.dataChangeLastModifiedTime = new Date(); + } + + @PreRemove + protected void preRemove() { + this.dataChangeLastModifiedTime = new Date(); + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/listener/ApolloAuditLogDataInfluenceEventListener.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/listener/ApolloAuditLogDataInfluenceEventListener.java new file mode 100644 index 00000000000..7042f9c456e --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/listener/ApolloAuditLogDataInfluenceEventListener.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.listener; + +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; +import com.ctrip.framework.apollo.audit.event.ApolloAuditLogDataInfluenceEvent; +import java.util.Collections; +import org.springframework.context.event.EventListener; + +public class ApolloAuditLogDataInfluenceEventListener { + + private final ApolloAuditLogApi api; + + public ApolloAuditLogDataInfluenceEventListener(ApolloAuditLogApi api) { + this.api = api; + } + + @EventListener + public void handleEvent(ApolloAuditLogDataInfluenceEvent event) { + Object e = event.getEntity(); + api.appendDataInfluences(Collections.singletonList(e), event.getBeanDefinition()); + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/repository/ApolloAuditLogDataInfluenceRepository.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/repository/ApolloAuditLogDataInfluenceRepository.java new file mode 100644 index 00000000000..88f5dd56037 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/repository/ApolloAuditLogDataInfluenceRepository.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.repository; + +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLogDataInfluence; +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.PagingAndSortingRepository; + +public interface ApolloAuditLogDataInfluenceRepository extends + PagingAndSortingRepository { + + List findBySpanId(String spanId); + + List findByInfluenceEntityNameAndInfluenceEntityId( + String influenceEntityName, String influenceEntityId, Pageable page); + + List findByInfluenceEntityNameAndInfluenceEntityIdAndFieldName( + String influenceEntityName, String influenceEntityId, String fieldName, Pageable page); + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/repository/ApolloAuditLogRepository.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/repository/ApolloAuditLogRepository.java new file mode 100644 index 00000000000..ccf93e66013 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/repository/ApolloAuditLogRepository.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.repository; + +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLog; +import java.util.Date; +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; + +public interface ApolloAuditLogRepository extends PagingAndSortingRepository { + + List findByTraceIdOrderByDataChangeCreatedTimeDesc(String traceId); + + List findByOpName(String opName, Pageable page); + + List findByOpNameAndDataChangeCreatedTimeGreaterThanEqualAndDataChangeCreatedTimeLessThanEqual( + String opName, Date startDate, Date endDate, Pageable pageable); + + List findByOpNameContainingOrOpTypeContainingOrOperatorContaining(String opName, + String opType, String operator, Pageable pageable); +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/service/ApolloAuditLogDataInfluenceService.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/service/ApolloAuditLogDataInfluenceService.java new file mode 100644 index 00000000000..14c1b6e72ca --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/service/ApolloAuditLogDataInfluenceService.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.service; + +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLogDataInfluence; +import com.ctrip.framework.apollo.audit.repository.ApolloAuditLogDataInfluenceRepository; +import java.util.List; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; + + +public class ApolloAuditLogDataInfluenceService { + + private final ApolloAuditLogDataInfluenceRepository dataInfluenceRepository; + + public ApolloAuditLogDataInfluenceService( + ApolloAuditLogDataInfluenceRepository dataInfluenceRepository) { + this.dataInfluenceRepository = dataInfluenceRepository; + } + + public ApolloAuditLogDataInfluence save(ApolloAuditLogDataInfluence dataInfluence) { + return dataInfluenceRepository.save(dataInfluence); + } + + public List findBySpanId(String spanId) { + return dataInfluenceRepository.findBySpanId(spanId); + } + + public List findByEntityNameAndEntityIdAndFieldName( + String entityName, String entityId, String fieldName, int page, int size) { + Pageable pageable = pageSortByTime(page, size); + return dataInfluenceRepository.findByInfluenceEntityNameAndInfluenceEntityIdAndFieldName( + entityName, entityId, fieldName, pageable); + } + + Pageable pageSortByTime(int page, int size) { + return PageRequest.of(page, size, Sort.by(new Order(Direction.DESC, "DataChangeCreatedTime"))); + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/service/ApolloAuditLogService.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/service/ApolloAuditLogService.java new file mode 100644 index 00000000000..f3b0f332786 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/service/ApolloAuditLogService.java @@ -0,0 +1,89 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.service; + +import com.ctrip.framework.apollo.audit.context.ApolloAuditSpan; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.repository.ApolloAuditLogRepository; +import java.util.Date; +import java.util.List; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; + +public class ApolloAuditLogService { + + private final ApolloAuditLogRepository logRepository; + + public ApolloAuditLogService(ApolloAuditLogRepository logRepository) { + this.logRepository = logRepository; + } + + public ApolloAuditLog save(ApolloAuditLog auditLog) { + return logRepository.save(auditLog); + } + + public void logSpan(ApolloAuditSpan span) { + + ApolloAuditLog auditLog = ApolloAuditLog.builder() + .traceId(span.traceId()) + .spanId(span.spanId()) + .parentSpanId(span.parentId()) + .followsFromSpanId(span.followsFromId()) + .operator(span.operator() != null ? span.operator() : "anonymous") + .opName(span.getOpName()) + .opType(span.getOpType().toString()) + .description(span.getDescription()) + .happenedTime(new Date()) + .build(); + logRepository.save(auditLog); + } + + public List findByTraceId(String traceId) { + return logRepository.findByTraceIdOrderByDataChangeCreatedTimeDesc(traceId); + } + + public List findAll(int page, int size) { + Pageable pageable = pageSortByTime(page, size); + return logRepository.findAll(pageable).getContent(); + } + + public List findByOpName(String opName, int page, int size) { + Pageable pageable = pageSortByTime(page, size); + return logRepository.findByOpName(opName, pageable); + } + + public List findByOpNameAndTime(String opName, Date startDate, + Date endDate, int page, int size) { + Pageable pageable = pageSortByTime(page, size); + return logRepository.findByOpNameAndDataChangeCreatedTimeGreaterThanEqualAndDataChangeCreatedTimeLessThanEqual( + opName, startDate, endDate, pageable); + } + + public List searchLogByNameOrTypeOrOperator(String query, int page, int size) { + Pageable pageable = pageSortByTime(page, size); + return logRepository.findByOpNameContainingOrOpTypeContainingOrOperatorContaining(query, query, + query, pageable); + } + + Pageable pageSortByTime(int page, int size) { + return PageRequest.of(page, size, Sort.by(new Order(Direction.DESC, "dataChangeCreatedTime"))); + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/ApolloAuditLogQueryApiPreAuthorizer.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/ApolloAuditLogQueryApiPreAuthorizer.java new file mode 100644 index 00000000000..8a06d59c3ec --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/ApolloAuditLogQueryApiPreAuthorizer.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.spi; + +public interface ApolloAuditLogQueryApiPreAuthorizer { + + boolean hasQueryPermission(); + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/ApolloAuditOperatorSupplier.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/ApolloAuditOperatorSupplier.java new file mode 100644 index 00000000000..00900b58d3b --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/ApolloAuditOperatorSupplier.java @@ -0,0 +1,23 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.spi; + +public interface ApolloAuditOperatorSupplier { + + String getOperator(); + +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/defaultimpl/ApolloAuditLogQueryApiDefaultPreAuthorizer.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/defaultimpl/ApolloAuditLogQueryApiDefaultPreAuthorizer.java new file mode 100644 index 00000000000..32de6765cd6 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/defaultimpl/ApolloAuditLogQueryApiDefaultPreAuthorizer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.spi.defaultimpl; + +import com.ctrip.framework.apollo.audit.spi.ApolloAuditLogQueryApiPreAuthorizer; + +public class ApolloAuditLogQueryApiDefaultPreAuthorizer implements + ApolloAuditLogQueryApiPreAuthorizer { + + @Override + public boolean hasQueryPermission() { + return true; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/defaultimpl/ApolloAuditOperatorDefaultSupplier.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/defaultimpl/ApolloAuditOperatorDefaultSupplier.java new file mode 100644 index 00000000000..458a5f43d13 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/spi/defaultimpl/ApolloAuditOperatorDefaultSupplier.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.spi.defaultimpl; + +import com.ctrip.framework.apollo.audit.constants.ApolloAuditConstants; +import com.ctrip.framework.apollo.audit.context.ApolloAuditTracer; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditOperatorSupplier; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +public class ApolloAuditOperatorDefaultSupplier implements ApolloAuditOperatorSupplier { + + @Override + public String getOperator() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes != null) { + Object tracer = requestAttributes.getAttribute(ApolloAuditConstants.TRACER, + RequestAttributes.SCOPE_REQUEST); + if (tracer != null) { + return ((ApolloAuditTracer) tracer).scopeManager().activeSpan().operator(); + } else { + return null; + } + } + return null; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/util/ApolloAuditUtil.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/util/ApolloAuditUtil.java new file mode 100644 index 00000000000..85c69e1c70b --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/util/ApolloAuditUtil.java @@ -0,0 +1,122 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.util; + +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTable; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLogDataInfluence; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.persistence.Id; + +public class ApolloAuditUtil { + + public static String generateId() { + return UUID.randomUUID().toString().replaceAll("-", ""); + } + + public static List getAnnotatedFields(Class annoClass, + Class clazz) { + return Arrays.stream(clazz.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(annoClass)).collect(Collectors.toList()); + } + + public static List toList(Object obj) { + if (obj instanceof Collection) { + Collection collection = (Collection) obj; + return new ArrayList<>(collection); + } else { + return Collections.singletonList(obj); + } + } + + public static String getApolloAuditLogTableName(Class clazz) { + return clazz.isAnnotationPresent(ApolloAuditLogDataInfluenceTable.class) ? clazz.getAnnotation( + ApolloAuditLogDataInfluenceTable.class).tableName() : null; + } + + public static Field getPersistenceIdFieldByAnnotation(Class clazz) { + while (clazz != null) { + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (field.isAnnotationPresent(Id.class)) { + field.setAccessible(true); + return field; + } + } + clazz = clazz.getSuperclass(); + } + return null; + } + + public static ApolloAuditLogDTO logToDTO(ApolloAuditLog auditLog) { + ApolloAuditLogDTO dto = new ApolloAuditLogDTO(); + dto.setId(auditLog.getId()); + dto.setOpType(auditLog.getOpType()); + dto.setOpName(auditLog.getOpName()); + dto.setDescription(auditLog.getDescription()); + dto.setOperator(auditLog.getOperator()); + dto.setHappenedTime(auditLog.getDataChangeCreatedTime()); + dto.setSpanId(auditLog.getSpanId()); + dto.setTraceId(auditLog.getTraceId()); + dto.setFollowsFromSpanId(auditLog.getFollowsFromSpanId()); + dto.setParentSpanId(auditLog.getParentSpanId()); + return dto; + } + + public static ApolloAuditLogDataInfluenceDTO dataInfluenceToDTO( + ApolloAuditLogDataInfluence dataInfluence) { + ApolloAuditLogDataInfluenceDTO dto = new ApolloAuditLogDataInfluenceDTO(); + dto.setId(dataInfluence.getId()); + dto.setInfluenceEntityName(dataInfluence.getInfluenceEntityName()); + dto.setInfluenceEntityId(dataInfluence.getInfluenceEntityId()); + dto.setFieldName(dataInfluence.getFieldName()); + dto.setFieldOldValue(dataInfluence.getFieldOldValue()); + dto.setFieldNewValue(dataInfluence.getFieldNewValue()); + dto.setHappenedTime(dataInfluence.getDataChangeCreatedTime()); + dto.setSpanId(dataInfluence.getSpanId()); + return dto; + } + + public static List logListToDTOList(List logList) { + List logDTOList = new ArrayList<>(); + logList.forEach(log -> { + logDTOList.add(logToDTO(log)); + }); + return logDTOList; + } + + public static List dataInfluenceListToDTOList( + List dataInfluenceList) { + List dataInfluenceDTOList = new ArrayList<>(); + dataInfluenceList.forEach(dataInfluence -> { + dataInfluenceDTOList.add(dataInfluenceToDTO(dataInfluence)); + }); + return dataInfluenceDTOList; + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/MockBeanFactory.java b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/MockBeanFactory.java new file mode 100644 index 00000000000..148a45b9794 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/MockBeanFactory.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit; + +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDetailsDTO; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLogDataInfluence; +import java.util.ArrayList; +import java.util.List; + +public class MockBeanFactory { + + public static ApolloAuditLogDTO mockAuditLogDTO() { + return new ApolloAuditLogDTO(); + } + + public static List mockAuditLogDTOListByLength(int length) { + List mockList = new ArrayList<>(); + for (int i = 0; i < length; i++) { + mockList.add(mockAuditLogDTO()); + } + return mockList; + } + + public static ApolloAuditLog mockAuditLog() { + return new ApolloAuditLog(); + } + + public static List mockAuditLogListByLength(int length) { + List mockList = new ArrayList<>(); + for (int i = 0; i < length; i++) { + mockList.add(mockAuditLog()); + } + return mockList; + } + + public static List mockTraceDetailsDTOListByLength(int length) { + List mockList = new ArrayList<>(); + for (int i = 0; i < length; i++) { + ApolloAuditLogDetailsDTO dto = new ApolloAuditLogDetailsDTO(); + dto.setLogDTO(mockAuditLogDTO()); + mockList.add(dto); + } + return mockList; + } + + public static ApolloAuditLogDataInfluenceDTO mockDataInfluenceDTO() { + return new ApolloAuditLogDataInfluenceDTO(); + } + + public static List mockDataInfluenceDTOListByLength(int length) { + List mockList = new ArrayList<>(); + for (int i = 0; i < length; i++) { + mockList.add(mockDataInfluenceDTO()); + } + return mockList; + } + + public static ApolloAuditLogDataInfluence mockDataInfluence() { + return new ApolloAuditLogDataInfluence(); + } + + public static List mockDataInfluenceListByLength(int length) { + List mockList = new ArrayList<>(); + for (int i = 0; i < length; i++) { + mockList.add(mockDataInfluence()); + } + return mockList; + } + + public static MockDataInfluenceEntity mockDataInfluenceEntity() { + return new MockDataInfluenceEntity(); + } + + public static List mockDataInfluenceEntityListByLength(int length) { + List mockList = new ArrayList<>(); + for (int i = 0; i < length; i++) { + MockDataInfluenceEntity e = mockDataInfluenceEntity(); + e.setId(i+1); + mockList.add(e); + } + return mockList; + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/MockDataInfluenceEntity.java b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/MockDataInfluenceEntity.java new file mode 100644 index 00000000000..387d08ce80d --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/MockDataInfluenceEntity.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit; + +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTable; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTableField; +import javax.persistence.Id; + +@ApolloAuditLogDataInfluenceTable(tableName = "MockTableName") +public class MockDataInfluenceEntity { + + @Id + private long id; + + @ApolloAuditLogDataInfluenceTableField(fieldName = "MarkedAttribute") + private String markedAttribute; + private String unMarkedAttribute; + private boolean isDeleted; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getMarkedAttribute() { + return markedAttribute; + } + + public void setMarkedAttribute(String markedAttribute) { + this.markedAttribute = markedAttribute; + } + + public String getUnMarkedAttribute() { + return unMarkedAttribute; + } + + public void setUnMarkedAttribute(String unMarkedAttribute) { + this.unMarkedAttribute = unMarkedAttribute; + } + + public boolean isDeleted() { + return isDeleted; + } + + public void setDeleted(boolean deleted) { + isDeleted = deleted; + } +} diff --git a/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditHttpInterceptorTest.java b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditHttpInterceptorTest.java new file mode 100644 index 00000000000..4d3212ed867 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditHttpInterceptorTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.component; + +import com.ctrip.framework.apollo.audit.context.ApolloAuditTraceContext; +import com.ctrip.framework.apollo.audit.context.ApolloAuditTracer; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLogDataInfluence; +import java.io.IOException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.test.context.ContextConfiguration; + +@SpringBootTest +@ContextConfiguration(classes = ApolloAuditHttpInterceptor.class) +public class ApolloAuditHttpInterceptorTest { + + @SpyBean + ApolloAuditHttpInterceptor interceptor; + + @MockBean + ApolloAuditTraceContext traceContext; + + @Test + public void testInterceptor() throws IOException { + ClientHttpRequestExecution execution = Mockito.mock(ClientHttpRequestExecution.class); + HttpRequest request = Mockito.mock(HttpRequest.class); + byte[] body = new byte[]{}; + ApolloAuditTracer tracer = Mockito.mock(ApolloAuditTracer.class); + HttpRequest mockInjected = Mockito.mock(HttpRequest.class); + + Mockito.when(traceContext.tracer()).thenReturn(tracer); + Mockito.when(tracer.inject(Mockito.eq(request))) + .thenReturn(mockInjected); + + interceptor.intercept(request, body, execution); + + Mockito.verify(execution, Mockito.times(1)) + .execute(Mockito.eq(mockInjected), Mockito.eq(body)); + } + + @Test + public void testInterceptorCaseNoTracer() throws IOException { + ClientHttpRequestExecution execution = Mockito.mock(ClientHttpRequestExecution.class); + HttpRequest request = Mockito.mock(HttpRequest.class); + byte[] body = new byte[]{}; + Mockito.when(traceContext.tracer()).thenReturn(null); + + interceptor.intercept(request, body, execution); + + Mockito.verify(execution, Mockito.times(1)) + .execute(Mockito.eq(request), Mockito.eq(body)); + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImplTest.java b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImplTest.java new file mode 100644 index 00000000000..f1ddd8e8f30 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImplTest.java @@ -0,0 +1,309 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.component; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.ctrip.framework.apollo.audit.MockBeanFactory; +import com.ctrip.framework.apollo.audit.MockDataInfluenceEntity; +import com.ctrip.framework.apollo.audit.annotation.OpType; +import com.ctrip.framework.apollo.audit.context.ApolloAuditScope; +import com.ctrip.framework.apollo.audit.context.ApolloAuditScopeManager; +import com.ctrip.framework.apollo.audit.context.ApolloAuditSpan; +import com.ctrip.framework.apollo.audit.context.ApolloAuditSpanContext; +import com.ctrip.framework.apollo.audit.context.ApolloAuditTraceContext; +import com.ctrip.framework.apollo.audit.context.ApolloAuditTracer; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDetailsDTO; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.entity.ApolloAuditLogDataInfluence; +import com.ctrip.framework.apollo.audit.service.ApolloAuditLogDataInfluenceService; +import com.ctrip.framework.apollo.audit.service.ApolloAuditLogService; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; + +@SpringBootTest +@ContextConfiguration(classes = ApolloAuditLogApiJpaImpl.class) +public class ApolloAuditLogApiJpaImplTest { + + // record api + + final OpType create = OpType.CREATE; + final OpType delete = OpType.DELETE; + final String opName = "test.create"; + final String traceId = "test-trace-id"; + final String spanId = "test-span-id"; + + final String entityId = "1"; + final String entityName = "App"; + final String fieldName = "name"; + final String fieldCurrentValue = "xxx"; + + final int entityNum = 3; + + // query api + + final int page = 0; + final int size = 10; + + @SpyBean + ApolloAuditLogApiJpaImpl api; + + @MockBean + ApolloAuditLogService logService; + @MockBean + ApolloAuditLogDataInfluenceService dataInfluenceService; + @MockBean + ApolloAuditTraceContext traceContext; + @MockBean + ApolloAuditTracer tracer; + + @Captor + private ArgumentCaptor influenceCaptor; + + @BeforeEach + void beforeEach() { + Mockito.reset(traceContext, tracer); + Mockito.when(traceContext.tracer()).thenReturn(tracer); + } + + @Test + public void testAppendAuditLog() { + final String description = "no description"; + { + ApolloAuditSpan activeSpan = new ApolloAuditSpan(); + activeSpan.setOpType(create); + activeSpan.setOpName(opName); + activeSpan.setContext(new ApolloAuditSpanContext(traceId, spanId)); + ApolloAuditScopeManager manager = new ApolloAuditScopeManager(); + ApolloAuditScope scope = new ApolloAuditScope(activeSpan, manager); + + Mockito.when(tracer.startActiveSpan(Mockito.eq(create), Mockito.eq(opName), Mockito.eq(description))) + .thenReturn(scope); + } + ApolloAuditScope scope = (ApolloAuditScope) api.appendAuditLog(create, opName); + + Mockito.verify(traceContext, Mockito.times(1)).tracer(); + Mockito.verify(tracer, Mockito.times(1)) + .startActiveSpan(Mockito.eq(create), Mockito.eq(opName), Mockito.eq(description)); + + assertEquals(create, scope.activeSpan().getOpType()); + assertEquals(opName, scope.activeSpan().getOpName()); + assertEquals(traceId, scope.activeSpan().traceId()); + assertEquals(spanId, scope.activeSpan().spanId()); + } + + @Test + public void testAppendDataInfluenceCaseCreateOrUpdate() { + { + ApolloAuditSpan span = Mockito.mock(ApolloAuditSpan.class); + Mockito.when(tracer.getActiveSpan()).thenReturn(span); + Mockito.when(span.spanId()).thenReturn(spanId); + Mockito.when(span.getOpType()).thenReturn(create); + } + + api.appendDataInfluence(entityName, entityId, fieldName, fieldCurrentValue); + + Mockito.verify(dataInfluenceService, Mockito.times(1)).save(influenceCaptor.capture()); + + ApolloAuditLogDataInfluence capturedInfluence = influenceCaptor.getValue(); + assertEquals(entityId, capturedInfluence.getInfluenceEntityId()); + assertEquals(entityName, capturedInfluence.getInfluenceEntityName()); + assertEquals(fieldName, capturedInfluence.getFieldName()); + assertNull(capturedInfluence.getFieldOldValue()); + assertEquals(fieldCurrentValue, capturedInfluence.getFieldNewValue()); + assertEquals(spanId, capturedInfluence.getSpanId()); + } + + @Test + public void testAppendDataInfluenceCaseDelete() { + { + ApolloAuditSpan span = Mockito.mock(ApolloAuditSpan.class); + Mockito.when(tracer.getActiveSpan()).thenReturn(span); + Mockito.when(span.spanId()).thenReturn(spanId); + Mockito.when(span.getOpType()).thenReturn(delete); + } + + api.appendDataInfluence(entityName, entityId, fieldName, fieldCurrentValue); + + Mockito.verify(dataInfluenceService, Mockito.times(1)).save(influenceCaptor.capture()); + + ApolloAuditLogDataInfluence capturedInfluence = influenceCaptor.getValue(); + assertEquals(entityId, capturedInfluence.getInfluenceEntityId()); + assertEquals(entityName, capturedInfluence.getInfluenceEntityName()); + assertEquals(fieldName, capturedInfluence.getFieldName()); + assertEquals(fieldCurrentValue, capturedInfluence.getFieldOldValue()); + assertNull(capturedInfluence.getFieldNewValue()); + assertEquals(spanId, capturedInfluence.getSpanId()); + } + + @Test + public void testAppendDataInfluenceCaseTracerIsNull() { + Mockito.when(traceContext.tracer()).thenReturn(null); + api.appendDataInfluence(entityName, entityId, fieldName, fieldCurrentValue); + Mockito.verify(traceContext, Mockito.times(1)).tracer(); + } + + @Test + public void testAppendDataInfluenceCaseActiveSpanIsNull() { + Mockito.when(tracer.getActiveSpan()).thenReturn(null); + api.appendDataInfluence(entityName, entityId, fieldName, fieldCurrentValue); + Mockito.verify(traceContext, Mockito.times(2)).tracer(); + Mockito.verify(tracer, Mockito.times(1)).getActiveSpan(); + } + + @Test + public void testAppendDataInfluences() { + List entities = MockBeanFactory.mockDataInfluenceEntityListByLength(entityNum); + api.appendDataInfluences(entities, MockDataInfluenceEntity.class); + + Mockito.verify(api, Mockito.times(entityNum)) + .appendDataInfluence(Mockito.anyString(), Mockito.eq("MockTableName"), Mockito.eq("MarkedAttribute"), + Mockito.any()); + } + + @Test + public void testAppendDataInfluencesCaseWrongBeanDefinition() { + List entities = new ArrayList<>(); + entities.add(new Object()); + assertThrows(IllegalArgumentException.class, () -> { + api.appendDataInfluences(entities, MockDataInfluenceEntity.class); + }); + + } + + @Test + public void testAppendDataInfluencesCaseIncompleteConditions() { + List entities = new ArrayList<>(entityNum); + + api.appendDataInfluences(entities, Object.class); + + Mockito.verify(api, Mockito.times(0)) + .appendDataInfluence(Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any()); + } + + + + @Test + public void testQueryLogs() { + { + List logList = MockBeanFactory.mockAuditLogListByLength(size); + Mockito.when(logService.findAll(Mockito.eq(page), Mockito.eq(size))) + .thenReturn(logList); + } + + List dtoList = api.queryLogs(page, size); + Mockito.verify(logService, Mockito.times(1)) + .findAll(Mockito.eq(page), Mockito.eq(size)); + assertEquals(size, dtoList.size()); + } + + @Test + public void testQueryLogsByOpNameCaseDateIsNull() { + final String opName = "query-op-name"; + final Date startDate = null; + final Date endDate = null; + { + List logList = MockBeanFactory.mockAuditLogListByLength(size); + Mockito.when(logService.findByOpName(Mockito.eq(opName), Mockito.eq(page), Mockito.eq(size))) + .thenReturn(logList); + } + + List dtoList = api.queryLogsByOpName(opName, startDate, endDate, page, size); + Mockito.verify(logService, Mockito.times(1)) + .findByOpName(Mockito.eq(opName), Mockito.eq(page), Mockito.eq(size)); + assertEquals(size, dtoList.size()); + } + + @Test + public void testQueryLogsByOpName() { + final String opName = "query-op-name"; + final Date startDate = new Date(); + final Date endDate = new Date(); + { + List logList = MockBeanFactory.mockAuditLogListByLength(size); + Mockito.when(logService.findByOpNameAndTime(Mockito.eq(opName), + Mockito.eq(startDate), Mockito.eq(endDate), Mockito.eq(page), Mockito.eq(size))) + .thenReturn(logList); + } + + List dtoList = api.queryLogsByOpName(opName, startDate, endDate, page, size); + Mockito.verify(logService, Mockito.times(1)) + .findByOpNameAndTime(Mockito.eq(opName), + Mockito.eq(startDate), Mockito.eq(endDate), Mockito.eq(page), Mockito.eq(size)); + assertEquals(size, dtoList.size()); + } + + @Test + public void testQueryTraceDetails() { + final String traceId = "query-trace-id"; + final int traceDetailsLength = 3; + final int dataInfluenceOfEachLog = 3; + { + List logList = MockBeanFactory.mockAuditLogListByLength(traceDetailsLength); + Mockito.when(logService.findByTraceId(Mockito.eq(traceId))) + .thenReturn(logList); + List dataInfluenceList = + MockBeanFactory.mockDataInfluenceListByLength(dataInfluenceOfEachLog); + Mockito.when(dataInfluenceService.findBySpanId(Mockito.any())) + .thenReturn(dataInfluenceList); + } + + List detailsDTOList = api.queryTraceDetails(traceId); + + Mockito.verify(logService, Mockito.times(1)) + .findByTraceId(Mockito.eq(traceId)); + Mockito.verify(dataInfluenceService, Mockito.times(3)) + .findBySpanId(Mockito.any()); + + assertEquals(traceDetailsLength, detailsDTOList.size()); + assertEquals(dataInfluenceOfEachLog, detailsDTOList.get(0).getDataInfluenceDTOList().size()); + } + + @Test + public void testQueryDataInfluencesByField() { + final String entityName = "App"; + final String entityId = "1"; + final String fieldName = "xxx"; + { + List dataInfluenceList = MockBeanFactory.mockDataInfluenceListByLength(size); + Mockito.when(dataInfluenceService.findByEntityNameAndEntityIdAndFieldName(Mockito.eq(entityName), + Mockito.eq(entityId), Mockito.eq(fieldName), Mockito.eq(page), Mockito.eq(size))) + .thenReturn(dataInfluenceList); + } + + List dtoList = api.queryDataInfluencesByField(entityName, entityId, fieldName, page, size); + Mockito.verify(dataInfluenceService, Mockito.times(1)) + .findByEntityNameAndEntityIdAndFieldName(Mockito.eq(entityName), + Mockito.eq(entityId), Mockito.eq(fieldName), Mockito.eq(page), Mockito.eq(size)); + assertEquals(size, dtoList.size()); + } +} diff --git a/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditScopeManagerTest.java b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditScopeManagerTest.java new file mode 100644 index 00000000000..d998f02c2d7 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/component/ApolloAuditScopeManagerTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.component; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import com.ctrip.framework.apollo.audit.context.ApolloAuditScope; +import com.ctrip.framework.apollo.audit.context.ApolloAuditScopeManager; +import com.ctrip.framework.apollo.audit.context.ApolloAuditSpan; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; + +@SpringBootTest +@ContextConfiguration(classes = ApolloAuditScopeManager.class) +public class ApolloAuditScopeManagerTest { + + @SpyBean + ApolloAuditScopeManager manager; + + @Test + public void testActivate() { + ApolloAuditSpan mockSpan = mock(ApolloAuditSpan.class); + ApolloAuditScope activeScope = manager.activate(mockSpan); + + verify(manager).setScope(eq(activeScope)); + assertEquals(mockSpan, activeScope.activeSpan()); + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTraceContextTest.java b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTraceContextTest.java new file mode 100644 index 00000000000..30ddb9bd39c --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTraceContextTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.context; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.ctrip.framework.apollo.audit.constants.ApolloAuditConstants; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditOperatorSupplier; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +@SpringBootTest +@ContextConfiguration(classes = ApolloAuditTraceContext.class) +public class ApolloAuditTraceContextTest { + + @SpyBean + ApolloAuditTraceContext traceContext; + + @MockBean + ApolloAuditOperatorSupplier supplier; + + @BeforeEach + public void beforeEach() { + // will be null of each unit-test begin + RequestContextHolder.resetRequestAttributes(); + Mockito.reset( + traceContext + ); + } + + @Test + public void testGetTracerNotInRequestThread() { + ApolloAuditTracer get = traceContext.tracer(); + assertNull(get); + } + + @Test + public void testGetTracerCaseNoTracerExistsInRequestThreads() { + RequestAttributes mockRequestAttributes = Mockito.mock(RequestAttributes.class); + RequestContextHolder.setRequestAttributes(mockRequestAttributes); + + ApolloAuditTracer get = traceContext.tracer(); + assertNotNull(get); + Mockito.verify(traceContext, Mockito.times(1)) + .setTracer(Mockito.any(ApolloAuditTracer.class)); + } + + @Test + public void testGetTracerInRequestThreads() { + ApolloAuditTracer mockTracer = new ApolloAuditTracer(Mockito.mock(ApolloAuditScopeManager.class), supplier); + RequestAttributes mockRequestAttributes = Mockito.mock(RequestAttributes.class); + RequestContextHolder.setRequestAttributes(mockRequestAttributes); + Mockito.when(mockRequestAttributes.getAttribute(Mockito.eq(ApolloAuditConstants.TRACER), Mockito.eq(RequestAttributes.SCOPE_REQUEST))) + .thenReturn(mockTracer); + ApolloAuditTracer get = traceContext.tracer(); + assertNotNull(get); + Mockito.verify(traceContext, Mockito.times(0)) + .setTracer(Mockito.any(ApolloAuditTracer.class)); + } + + @Test + public void testGetTracerInAnotherThreadButSameRequest() { + ApolloAuditTracer mockTracer = Mockito.mock(ApolloAuditTracer.class); + { + Mockito.when(traceContext.tracer()).thenReturn(mockTracer); + } + CountDownLatch latch = new CountDownLatch(1); + Executors.newSingleThreadExecutor().submit(() -> { + ApolloAuditTracer tracer = traceContext.tracer(); + + assertEquals(mockTracer, tracer); + + latch.countDown(); + }); + } + + @Test + public void testGetTracerInAnotherRequest() { + ApolloAuditTracer mockTracer = Mockito.mock(ApolloAuditTracer.class); + { + Mockito.when(traceContext.tracer()).thenReturn(mockTracer); + } + CountDownLatch latch = new CountDownLatch(1); + Executors.newSingleThreadExecutor().submit(() -> { + RequestContextHolder.resetRequestAttributes(); + ApolloAuditTracer tracer = traceContext.tracer(); + + assertNotEquals(mockTracer, tracer); + + latch.countDown(); + }); + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTracerTest.java b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTracerTest.java new file mode 100644 index 00000000000..346e5d20282 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/context/ApolloAuditTracerTest.java @@ -0,0 +1,251 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.context; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; + +import com.ctrip.framework.apollo.audit.annotation.OpType; +import com.ctrip.framework.apollo.audit.constants.ApolloAuditConstants; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditOperatorSupplier; +import javax.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +@SpringBootTest +@ContextConfiguration(classes = ApolloAuditTracer.class) +public class ApolloAuditTracerTest { + + final OpType opType = OpType.CREATE; + final String opName = "create"; + final String description = "description"; + + final String activeTraceId = "10001"; + final String activeSpanId = "100010001"; + final String operator = "luke"; + + @SpyBean + ApolloAuditTracer tracer; + + @MockBean + ApolloAuditScopeManager manager; + @MockBean + ApolloAuditOperatorSupplier supplier; + + @BeforeEach + public void beforeEach() { + RequestContextHolder.resetRequestAttributes(); + Mockito.reset( + tracer, + manager, + supplier + ); + } + + @Test + public void testInject() { + + HttpRequest mockRequest = Mockito.mock(HttpRequest.class); + { + ApolloAuditSpan activeSpan = Mockito.mock(ApolloAuditSpan.class); + Mockito.when(manager.activeSpan()).thenReturn(activeSpan); + Mockito.when(mockRequest.getHeaders()).thenReturn(new HttpHeaders()); + } + HttpRequest injected = tracer.inject(mockRequest); + + HttpHeaders headers = injected.getHeaders(); + assertNotNull(headers.get(ApolloAuditConstants.TRACE_ID)); + assertNotNull(headers.get(ApolloAuditConstants.SPAN_ID)); + assertNotNull(headers.get(ApolloAuditConstants.OPERATOR)); + assertNotNull(headers.get(ApolloAuditConstants.PARENT_ID)); + assertNotNull(headers.get(ApolloAuditConstants.FOLLOWS_FROM_ID)); + } + + @Test + public void testInjectCaseActiveSpanIsNull() { + HttpRequest mockRequest = Mockito.mock(HttpRequest.class); + { + Mockito.when(manager.activeSpan()).thenReturn(null); + Mockito.when(mockRequest.getHeaders()).thenReturn(new HttpHeaders()); + } + HttpRequest injected = tracer.inject(mockRequest); + + HttpHeaders headers = injected.getHeaders(); + assertNull(headers.get(ApolloAuditConstants.TRACE_ID)); + assertNull(headers.get(ApolloAuditConstants.SPAN_ID)); + assertNull(headers.get(ApolloAuditConstants.OPERATOR)); + assertNull(headers.get(ApolloAuditConstants.PARENT_ID)); + assertNull(headers.get(ApolloAuditConstants.FOLLOWS_FROM_ID)); + } + + @Test + public void testStartSpanCaseActiveSpanExistsAndNoFollowsFrom() { + { + // has parent span + ApolloAuditSpan activeSpan = Mockito.mock(ApolloAuditSpan.class); + Mockito.when(tracer.getActiveSpan()).thenReturn(activeSpan); + Mockito.when(activeSpan.traceId()).thenReturn(activeTraceId); + Mockito.when(activeSpan.spanId()).thenReturn(activeSpanId); + Mockito.when(activeSpan.operator()).thenReturn(operator); + // not follows from any span + ApolloAuditScope mockScope = Mockito.mock(ApolloAuditScope.class); + Mockito.when(manager.getScope()).thenReturn(mockScope); + Mockito.when(mockScope.getLastSpanId()).thenReturn(null); + } + + ApolloAuditSpan build = tracer.startSpan(opType, opName, description); + + assertEquals(opType, build.getOpType()); + assertEquals(opName, build.getOpName()); + assertEquals(description, build.getDescription()); + assertEquals(activeTraceId, build.traceId()); + assertEquals(activeSpanId, build.parentId()); + assertEquals(operator, build.operator()); + assertNotNull(build.spanId()); + assertNull(build.followsFromId()); + } + + @Test + public void testStartSpanCaseActiveSpanExistsAndHasFollowsFrom() { + final String followsFromSpanId = "100010000"; + { + // has parent span + ApolloAuditSpan activeSpan = Mockito.mock(ApolloAuditSpan.class); + Mockito.when(tracer.getActiveSpan()).thenReturn(activeSpan); + Mockito.when(activeSpan.traceId()).thenReturn(activeTraceId); + Mockito.when(activeSpan.spanId()).thenReturn(activeSpanId); + Mockito.when(activeSpan.operator()).thenReturn(operator); + // has follows from span + ApolloAuditScope mockScope = Mockito.mock(ApolloAuditScope.class); + Mockito.when(manager.getScope()).thenReturn(mockScope); + Mockito.when(mockScope.getLastSpanId()).thenReturn(followsFromSpanId); + } + + ApolloAuditSpan build = tracer.startSpan(opType, opName, description); + + assertEquals(opType, build.getOpType()); + assertEquals(opName, build.getOpName()); + assertEquals(description, build.getDescription()); + assertEquals(activeTraceId, build.traceId()); + assertEquals(activeSpanId, build.parentId()); + assertEquals(operator, build.operator()); + assertNotNull(build.spanId()); + assertEquals(followsFromSpanId, build.followsFromId()); + } + + @Test + public void testStartSpanCaseNoActiveSpanExists() { + { + // no parent span + Mockito.when(tracer.getActiveSpan()).thenReturn(null); + // is the origin of a trace, need to get operator + Mockito.when(supplier.getOperator()).thenReturn(operator); + // of course no scope at this time + Mockito.when(manager.getScope()).thenReturn(null); + } + + ApolloAuditSpan build = tracer.startSpan(opType, opName, description); + + assertEquals(opType, build.getOpType()); + assertEquals(opName, build.getOpName()); + assertEquals(description, build.getDescription()); + assertNotNull(build.traceId()); + assertNotNull(build.spanId()); + assertNull(build.parentId()); + assertEquals(operator, build.operator()); + assertNull(build.followsFromId()); + } + + @Test + public void testStartActiveSpan() { + ApolloAuditSpan activeSpan = Mockito.mock(ApolloAuditSpan.class); + { + doReturn(activeSpan).when(tracer).startSpan(Mockito.eq(opType), Mockito.eq(opName), Mockito.eq(description)); + } + tracer.startActiveSpan(opType, opName, description); + Mockito.verify(tracer, Mockito.times(1)) + .startSpan(Mockito.eq(opType), Mockito.eq(opName), Mockito.eq(description)); + Mockito.verify(manager, times(1)) + .activate(Mockito.eq(activeSpan)); + } + + @Test + public void testGetActiveSpanFromContext() { + ApolloAuditSpan activeSpan = Mockito.mock(ApolloAuditSpan.class); + { + Mockito.when(manager.activeSpan()).thenReturn(activeSpan); + } + ApolloAuditSpan get = tracer.getActiveSpan(); + assertEquals(activeSpan, get); + } + + @Test + public void testGetActiveSpanFromHttpRequestCaseNotInRequestThread() { + { + // no span would be in context + Mockito.when(manager.activeSpan()).thenReturn(null); + // not in request thread + } + ApolloAuditSpan get = tracer.getActiveSpan(); + assertNull(get); + } + + @Test + public void testGetActiveSpanFromHttpRequestCaseInRequestThread() { + final String httpParentId = "100010002"; + final String httpFollowsFromId = "100010003"; + { + // no span would be in context + Mockito.when(manager.activeSpan()).thenReturn(null); + // in request thread + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + Mockito.when(request.getHeader(Mockito.eq(ApolloAuditConstants.TRACE_ID))) + .thenReturn(activeTraceId); + Mockito.when(request.getHeader(Mockito.eq(ApolloAuditConstants.SPAN_ID))) + .thenReturn(activeSpanId); + Mockito.when(request.getHeader(Mockito.eq(ApolloAuditConstants.OPERATOR))) + .thenReturn(operator); + Mockito.when(request.getHeader(Mockito.eq(ApolloAuditConstants.PARENT_ID))) + .thenReturn(httpParentId); + Mockito.when(request.getHeader(Mockito.eq(ApolloAuditConstants.FOLLOWS_FROM_ID))) + .thenReturn(httpFollowsFromId); + } + ApolloAuditSpan get = tracer.getActiveSpan(); + assertEquals(activeTraceId, get.traceId()); + assertEquals(activeSpanId, get.spanId()); + assertEquals(operator, get.operator()); + assertEquals(httpParentId, get.parentId()); + assertEquals(httpFollowsFromId, get.followsFromId()); + + assertNull(get.getOpType()); + assertNull(get.getOpName()); + } + +} diff --git a/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/controller/ApolloAuditControllerTest.java b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/controller/ApolloAuditControllerTest.java new file mode 100644 index 00000000000..6cc7dc96ab2 --- /dev/null +++ b/apollo-audit/apollo-audit-impl/src/test/java/com/ctrip/framework/apollo/audit/controller/ApolloAuditControllerTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.controller; + +import com.ctrip.framework.apollo.audit.ApolloAuditProperties; +import com.ctrip.framework.apollo.audit.MockBeanFactory; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO; +import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDetailsDTO; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +@WebMvcTest +@ContextConfiguration(classes = ApolloAuditController.class) +public class ApolloAuditControllerTest { + + final int page = 0; + final int size = 10; + @Autowired + ApolloAuditController apolloAuditController; + @Autowired + private MockMvc mockMvc; + @MockBean + private ApolloAuditLogApi api; + @MockBean + private ApolloAuditProperties properties; + + @Test + public void testFindAllAuditLogs() throws Exception { + + { + List mockLogDTOList = MockBeanFactory.mockAuditLogDTOListByLength(size); + Mockito.when(api.queryLogs(Mockito.eq(page), Mockito.eq(size))).thenReturn(mockLogDTOList); + } + + mockMvc.perform(MockMvcRequestBuilders.get("/apollo/audit/logs") + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$").isArray()) + .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(size)); + + Mockito.verify(api, Mockito.times(1)).queryLogs(Mockito.eq(page), Mockito.eq(size)); + } + + @Test + public void testFindTraceDetails() throws Exception { + final String traceId = "query-trace-id"; + final int traceDetailsListLength = 3; + { + List mockDetailsDTOList = MockBeanFactory.mockTraceDetailsDTOListByLength( + traceDetailsListLength); + mockDetailsDTOList.forEach(e -> e.getLogDTO().setTraceId(traceId)); + Mockito.when(api.queryTraceDetails(Mockito.eq(traceId))).thenReturn(mockDetailsDTOList); + } + + mockMvc.perform(MockMvcRequestBuilders.get("/apollo/audit/trace") + .param("traceId", traceId)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$").isArray()) + .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(traceDetailsListLength)) + .andExpect(MockMvcResultMatchers.jsonPath("$[0].logDTO.traceId").value(traceId)); + + Mockito.verify(api, Mockito.times(1)).queryTraceDetails(Mockito.eq(traceId)); + } + + @Test + public void testFindAllAuditLogsByOpNameAndTime() throws Exception { + final String opName = "query-op-name"; + final Date startDate = new Date(2023, Calendar.OCTOBER, 15); + final Date endDate = new Date(2023, Calendar.OCTOBER, 16); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); + + { + List mockLogDTOList = MockBeanFactory.mockAuditLogDTOListByLength(size); + mockLogDTOList.forEach(e -> { + e.setOpName(opName); + }); + Mockito.when( + api.queryLogsByOpName(Mockito.eq(opName), Mockito.eq(startDate), Mockito.eq(endDate), + Mockito.eq(page), Mockito.eq(size))).thenReturn(mockLogDTOList); + } + + mockMvc.perform(MockMvcRequestBuilders.get("/apollo/audit/logs/opName").param("opName", opName) + .param("startDate", sdf.format(startDate)) + .param("endDate", sdf.format(endDate)) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$").isArray()) + .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(size)) + .andExpect(MockMvcResultMatchers.jsonPath("$.[0].opName").value(opName)); + + Mockito.verify(api, Mockito.times(1)) + .queryLogsByOpName(Mockito.eq(opName), Mockito.eq(startDate), Mockito.eq(endDate), + Mockito.eq(page), Mockito.eq(size)); + } + + @Test + public void testFindDataInfluencesByField() throws Exception { + final String entityName = "query-entity-name"; + final String entityId = "query-entity-id"; + final String fieldName = "query-field-name"; + { + List mockDataInfluenceDTOList = MockBeanFactory.mockDataInfluenceDTOListByLength( + size); + mockDataInfluenceDTOList.forEach(e -> { + e.setInfluenceEntityName(entityName); + e.setInfluenceEntityId(entityId); + e.setFieldName(fieldName); + }); + Mockito.when(api.queryDataInfluencesByField(Mockito.eq(entityName), Mockito.eq(entityId), + Mockito.eq(fieldName), Mockito.eq(page), Mockito.eq(size))) + .thenReturn(mockDataInfluenceDTOList); + } + + mockMvc.perform(MockMvcRequestBuilders.get("/apollo/audit/logs/dataInfluences/field") + .param("entityName", entityName) + .param("entityId", entityId) + .param("fieldName", fieldName) + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size))) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.jsonPath("$").isArray()) + .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(size)); + + Mockito.verify(api, Mockito.times(1)) + .queryDataInfluencesByField(Mockito.eq(entityName), Mockito.eq(entityId), + Mockito.eq(fieldName), Mockito.eq(page), Mockito.eq(size)); + } + + +} diff --git a/apollo-audit/apollo-audit-spring-boot-starter/pom.xml b/apollo-audit/apollo-audit-spring-boot-starter/pom.xml new file mode 100644 index 00000000000..11e52f2dd77 --- /dev/null +++ b/apollo-audit/apollo-audit-spring-boot-starter/pom.xml @@ -0,0 +1,47 @@ + + + + + apollo-audit + com.ctrip.framework.apollo + ${revision} + + 4.0.0 + + apollo-audit-spring-boot-starter + ${revision} + + + + com.ctrip.framework.apollo + apollo-audit-impl + + third party<--> + + org.springframework.boot + spring-boot-autoconfigure + + + org.aspectj + aspectjrt + + + + \ No newline at end of file diff --git a/apollo-audit/apollo-audit-spring-boot-starter/src/main/java/com/ctrip/framework/apollo/audit/configuration/ApolloAuditAutoConfiguration.java b/apollo-audit/apollo-audit-spring-boot-starter/src/main/java/com/ctrip/framework/apollo/audit/configuration/ApolloAuditAutoConfiguration.java new file mode 100644 index 00000000000..c8e064e88f4 --- /dev/null +++ b/apollo-audit/apollo-audit-spring-boot-starter/src/main/java/com/ctrip/framework/apollo/audit/configuration/ApolloAuditAutoConfiguration.java @@ -0,0 +1,117 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.configuration; + +import com.ctrip.framework.apollo.audit.ApolloAuditProperties; +import com.ctrip.framework.apollo.audit.ApolloAuditRegistrar; +import com.ctrip.framework.apollo.audit.aop.ApolloAuditSpanAspect; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; +import com.ctrip.framework.apollo.audit.component.ApolloAuditHttpInterceptor; +import com.ctrip.framework.apollo.audit.component.ApolloAuditLogApiJpaImpl; +import com.ctrip.framework.apollo.audit.context.ApolloAuditTraceContext; +import com.ctrip.framework.apollo.audit.controller.ApolloAuditController; +import com.ctrip.framework.apollo.audit.listener.ApolloAuditLogDataInfluenceEventListener; +import com.ctrip.framework.apollo.audit.repository.ApolloAuditLogDataInfluenceRepository; +import com.ctrip.framework.apollo.audit.repository.ApolloAuditLogRepository; +import com.ctrip.framework.apollo.audit.service.ApolloAuditLogDataInfluenceService; +import com.ctrip.framework.apollo.audit.service.ApolloAuditLogService; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditLogQueryApiPreAuthorizer; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditOperatorSupplier; +import com.ctrip.framework.apollo.audit.spi.defaultimpl.ApolloAuditLogQueryApiDefaultPreAuthorizer; +import com.ctrip.framework.apollo.audit.spi.defaultimpl.ApolloAuditOperatorDefaultSupplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@EnableConfigurationProperties(ApolloAuditProperties.class) +@Import(ApolloAuditRegistrar.class) +@ConditionalOnProperty(prefix = "apollo.audit.log", name = "enabled", havingValue = "true") +public class ApolloAuditAutoConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(ApolloAuditAutoConfiguration.class); + + private final ApolloAuditProperties apolloAuditProperties; + + public ApolloAuditAutoConfiguration(ApolloAuditProperties apolloAuditProperties) { + this.apolloAuditProperties = apolloAuditProperties; + logger.info("ApolloAuditAutoConfigure initializing..."); + } + + @Bean + public ApolloAuditLogDataInfluenceService apolloAuditLogDataInfluenceService( + ApolloAuditLogDataInfluenceRepository dataInfluenceRepository) { + return new ApolloAuditLogDataInfluenceService(dataInfluenceRepository); + } + + @Bean + public ApolloAuditLogService apolloAuditLogService(ApolloAuditLogRepository logRepository) { + return new ApolloAuditLogService(logRepository); + } + + @Bean + @ConditionalOnMissingBean(ApolloAuditOperatorSupplier.class) + public ApolloAuditOperatorSupplier apolloAuditLogOperatorSupplier() { + return new ApolloAuditOperatorDefaultSupplier(); + } + + @Bean + public ApolloAuditTraceContext apolloAuditTraceContext( + ApolloAuditOperatorSupplier apolloAuditLogOperatorSupplier) { + return new ApolloAuditTraceContext(apolloAuditLogOperatorSupplier); + } + + @Bean + public ApolloAuditLogApi apolloAuditLogApi(ApolloAuditLogService logService, + ApolloAuditLogDataInfluenceService dataInfluenceService, + ApolloAuditTraceContext apolloAuditTraceContext) { + return new ApolloAuditLogApiJpaImpl(logService, dataInfluenceService, apolloAuditTraceContext); + } + + @Bean + public ApolloAuditSpanAspect apolloAuditSpanAspect(ApolloAuditLogApi apolloAuditLogApi) { + return new ApolloAuditSpanAspect(apolloAuditLogApi); + } + + @Bean + public ApolloAuditHttpInterceptor apolloAuditHttpInterceptor( + ApolloAuditTraceContext traceContext) { + return new ApolloAuditHttpInterceptor(traceContext); + } + + @Bean(name = "apolloAuditLogQueryApiPreAuthorizer") + @ConditionalOnMissingBean(ApolloAuditLogQueryApiPreAuthorizer.class) + public ApolloAuditLogQueryApiPreAuthorizer apolloAuditLogQueryApiPreAuthorizer() { + return new ApolloAuditLogQueryApiDefaultPreAuthorizer(); + } + + @Bean + public ApolloAuditController apolloAuditController(ApolloAuditLogApi api, ApolloAuditProperties apolloAuditProperties) { + return new ApolloAuditController(api, apolloAuditProperties); + } + + @Bean + public ApolloAuditLogDataInfluenceEventListener apolloAuditLogDataInfluenceEventListener( + ApolloAuditLogApi api) { + return new ApolloAuditLogDataInfluenceEventListener(api); + } +} diff --git a/apollo-audit/apollo-audit-spring-boot-starter/src/main/java/com/ctrip/framework/apollo/audit/configuration/ApolloAuditNoOpAutoConfiguration.java b/apollo-audit/apollo-audit-spring-boot-starter/src/main/java/com/ctrip/framework/apollo/audit/configuration/ApolloAuditNoOpAutoConfiguration.java new file mode 100644 index 00000000000..184bd5f7bdc --- /dev/null +++ b/apollo-audit/apollo-audit-spring-boot-starter/src/main/java/com/ctrip/framework/apollo/audit/configuration/ApolloAuditNoOpAutoConfiguration.java @@ -0,0 +1,82 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.audit.configuration; + +import com.ctrip.framework.apollo.audit.ApolloAuditProperties; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; +import com.ctrip.framework.apollo.audit.component.ApolloAuditHttpInterceptor; +import com.ctrip.framework.apollo.audit.component.ApolloAuditLogApiNoOpImpl; +import com.ctrip.framework.apollo.audit.context.ApolloAuditTraceContext; +import com.ctrip.framework.apollo.audit.controller.ApolloAuditController; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditLogQueryApiPreAuthorizer; +import com.ctrip.framework.apollo.audit.spi.ApolloAuditOperatorSupplier; +import com.ctrip.framework.apollo.audit.spi.defaultimpl.ApolloAuditLogQueryApiDefaultPreAuthorizer; +import com.ctrip.framework.apollo.audit.spi.defaultimpl.ApolloAuditOperatorDefaultSupplier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(ApolloAuditProperties.class) +@ConditionalOnProperty(prefix = "apollo.audit.log", name = "enabled", havingValue = "false", matchIfMissing = true) +public class ApolloAuditNoOpAutoConfiguration { + + private final ApolloAuditProperties apolloAuditProperties; + + public ApolloAuditNoOpAutoConfiguration(ApolloAuditProperties apolloAuditProperties) { + this.apolloAuditProperties = apolloAuditProperties; + } + + @Bean + @ConditionalOnMissingBean(ApolloAuditLogApi.class) + public ApolloAuditLogApi apolloAuditLogApi() { + return new ApolloAuditLogApiNoOpImpl(); + } + + @Bean + @ConditionalOnMissingBean(ApolloAuditOperatorSupplier.class) + public ApolloAuditOperatorSupplier apolloAuditLogOperatorSupplier() { + return new ApolloAuditOperatorDefaultSupplier(); + } + + @Bean + @ConditionalOnMissingBean(ApolloAuditTraceContext.class) + public ApolloAuditTraceContext apolloAuditTraceContext(ApolloAuditOperatorSupplier supplier) { + return new ApolloAuditTraceContext(supplier); + } + + @Bean + @ConditionalOnMissingBean(ApolloAuditHttpInterceptor.class) + public ApolloAuditHttpInterceptor apolloAuditLogHttpInterceptor( + ApolloAuditTraceContext traceContext) { + return new ApolloAuditHttpInterceptor(traceContext); + } + + @Bean(name = "apolloAuditLogQueryApiPreAuthorizer") + @ConditionalOnMissingBean(ApolloAuditLogQueryApiPreAuthorizer.class) + public ApolloAuditLogQueryApiPreAuthorizer apolloAuditLogQueryApiPreAuthorizer() { + return new ApolloAuditLogQueryApiDefaultPreAuthorizer(); + } + + @Bean + public ApolloAuditController apolloAuditController(ApolloAuditLogApi api, ApolloAuditProperties apolloAuditProperties) { + return new ApolloAuditController(api, apolloAuditProperties); + } + +} diff --git a/apollo-audit/apollo-audit-spring-boot-starter/src/main/resources/META-INF/spring.factories b/apollo-audit/apollo-audit-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..d442dd2cfb7 --- /dev/null +++ b/apollo-audit/apollo-audit-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.ctrip.framework.apollo.audit.configuration.ApolloAuditAutoConfiguration,\ + com.ctrip.framework.apollo.audit.configuration.ApolloAuditNoOpAutoConfiguration \ No newline at end of file diff --git a/apollo-audit/pom.xml b/apollo-audit/pom.xml new file mode 100644 index 00000000000..e360a37344a --- /dev/null +++ b/apollo-audit/pom.xml @@ -0,0 +1,42 @@ + + + + + apollo + com.ctrip.framework.apollo + ${revision} + + 4.0.0 + + apollo-audit + pom + Apollo Audit + + apollo-audit-annotation + apollo-audit-impl + apollo-audit-api + apollo-audit-spring-boot-starter + + + + ${project.artifactId} + + + \ No newline at end of file diff --git a/apollo-biz/pom.xml b/apollo-biz/pom.xml index 737e82707d2..e92a2839784 100644 --- a/apollo-biz/pom.xml +++ b/apollo-biz/pom.xml @@ -34,6 +34,15 @@ com.ctrip.framework.apollo apollo-common + + com.ctrip.framework.apollo + apollo-audit-api + + + com.ctrip.framework.apollo + apollo-audit-spring-boot-starter + test + org.springframework.cloud diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AppService.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AppService.java index 14fa621cb5b..1da9d1f7754 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AppService.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AppService.java @@ -16,6 +16,8 @@ */ package com.ctrip.framework.apollo.biz.service; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.annotation.OpType; import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.repository.AppRepository; import com.ctrip.framework.apollo.common.entity.App; @@ -46,16 +48,16 @@ public boolean isAppIdUnique(String appId) { } @Transactional + @ApolloAuditLog(type = OpType.DELETE, name = "App.delete") public void delete(long id, String operator) { + App app = appRepository.findById(id).orElse(null); if (app == null) { return; } - app.setDeleted(true); app.setDataChangeLastModifiedBy(operator); appRepository.save(app); - auditService.audit(App.class.getSimpleName(), id, Audit.OP.DELETE, operator); } @@ -73,6 +75,7 @@ public App findOne(String appId) { } @Transactional + @ApolloAuditLog(type = OpType.CREATE, name = "App.create") public App save(App entity) { if (!isAppIdUnique(entity.getAppId())) { throw new ServiceException("appId not unique"); @@ -87,6 +90,7 @@ public App save(App entity) { } @Transactional + @ApolloAuditLog(type = OpType.UPDATE, name = "App.update") public void update(App app) { String appId = app.getAppId(); @@ -103,7 +107,6 @@ public void update(App app) { managedApp.setDataChangeLastModifiedBy(app.getDataChangeLastModifiedBy()); managedApp = appRepository.save(managedApp); - auditService.audit(App.class.getSimpleName(), managedApp.getId(), Audit.OP.UPDATE, managedApp.getDataChangeLastModifiedBy()); diff --git a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/DatabaseDiscoveryIntegrationTest.java b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/DatabaseDiscoveryIntegrationTest.java index 39cdb04bbcb..f67d0581f04 100644 --- a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/DatabaseDiscoveryIntegrationTest.java +++ b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/DatabaseDiscoveryIntegrationTest.java @@ -22,14 +22,11 @@ import com.ctrip.framework.apollo.biz.AbstractIntegrationTest; import com.ctrip.framework.apollo.biz.registry.configuration.ApolloServiceDiscoveryAutoConfiguration; import com.ctrip.framework.apollo.biz.registry.configuration.ApolloServiceRegistryAutoConfiguration; -import com.ctrip.framework.apollo.biz.repository.ServiceRegistryRepository; - import java.util.List; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; @@ -49,7 +46,6 @@ ApolloServiceRegistryAutoConfiguration.class, ApolloServiceDiscoveryAutoConfiguration.class, }) -@EnableJpaRepositories(basePackageClasses = ServiceRegistryRepository.class) public class DatabaseDiscoveryIntegrationTest extends AbstractIntegrationTest { private final Logger log = LoggerFactory.getLogger(this.getClass()); diff --git a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/DatabaseDiscoveryWithoutDecoratorIntegrationTest.java b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/DatabaseDiscoveryWithoutDecoratorIntegrationTest.java index 8d60a414d7c..98668c6dc89 100644 --- a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/DatabaseDiscoveryWithoutDecoratorIntegrationTest.java +++ b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/DatabaseDiscoveryWithoutDecoratorIntegrationTest.java @@ -24,7 +24,6 @@ import com.ctrip.framework.apollo.biz.registry.configuration.ApolloServiceDiscoveryAutoConfiguration; import com.ctrip.framework.apollo.biz.registry.configuration.ApolloServiceRegistryAutoConfiguration; import com.ctrip.framework.apollo.biz.registry.configuration.support.ApolloServiceDiscoveryProperties; -import com.ctrip.framework.apollo.biz.repository.ServiceRegistryRepository; import com.ctrip.framework.apollo.biz.service.ServiceRegistryService; import java.util.List; import org.junit.Test; @@ -37,7 +36,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; @@ -62,7 +60,6 @@ ApolloServiceDiscoveryWithoutDecoratorAutoConfiguration.class, ApolloServiceDiscoveryAutoConfiguration.class, }) -@EnableJpaRepositories(basePackageClasses = ServiceRegistryRepository.class) public class DatabaseDiscoveryWithoutDecoratorIntegrationTest extends AbstractIntegrationTest { private final Logger log = LoggerFactory.getLogger(this.getClass()); diff --git a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/configuration/support/ApolloServiceRegistryClearApplicationRunnerIntegrationTest.java b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/configuration/support/ApolloServiceRegistryClearApplicationRunnerIntegrationTest.java index d57469b3cdc..26a2a8438a1 100644 --- a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/configuration/support/ApolloServiceRegistryClearApplicationRunnerIntegrationTest.java +++ b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/registry/configuration/support/ApolloServiceRegistryClearApplicationRunnerIntegrationTest.java @@ -27,7 +27,6 @@ import java.util.List; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; @@ -44,7 +43,6 @@ ApolloServiceRegistryAutoConfiguration.class, ApolloServiceDiscoveryAutoConfiguration.class, }) -@EnableJpaRepositories(basePackageClasses = ServiceRegistryRepository.class) public class ApolloServiceRegistryClearApplicationRunnerIntegrationTest extends AbstractIntegrationTest { diff --git a/apollo-common/pom.xml b/apollo-common/pom.xml index 6eb8be96913..874ea785e76 100644 --- a/apollo-common/pom.xml +++ b/apollo-common/pom.xml @@ -34,6 +34,10 @@ com.ctrip.framework.apollo apollo-core + + com.ctrip.framework.apollo + apollo-audit-api + org.springframework.boot spring-boot-starter-actuator diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/App.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/App.java index 0509c647a16..68393ff7bcd 100644 --- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/App.java +++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/App.java @@ -16,6 +16,8 @@ */ package com.ctrip.framework.apollo.common.entity; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTable; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTableField; import com.ctrip.framework.apollo.common.utils.InputValidator; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; @@ -30,10 +32,12 @@ @Table(name = "`App`") @SQLDelete(sql = "Update App set IsDeleted = true, DeletedAt = ROUND(UNIX_TIMESTAMP(NOW(4))*1000) where Id = ?") @Where(clause = "`IsDeleted` = false") +@ApolloAuditLogDataInfluenceTable(tableName = "App") public class App extends BaseEntity { @NotBlank(message = "Name cannot be blank") @Column(name = "`Name`", nullable = false) + @ApolloAuditLogDataInfluenceTableField(fieldName = "Name") private String name; @NotBlank(message = "AppId cannot be blank") @@ -42,6 +46,7 @@ public class App extends BaseEntity { message = InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE ) @Column(name = "`AppId`", nullable = false) + @ApolloAuditLogDataInfluenceTableField(fieldName = "AppId") private String appId; @Column(name = "`OrgId`", nullable = false) diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/AppNamespace.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/AppNamespace.java index 4c2c9cdb68f..deab2dda7f5 100644 --- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/AppNamespace.java +++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/AppNamespace.java @@ -17,6 +17,8 @@ package com.ctrip.framework.apollo.common.entity; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTable; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTableField; import com.ctrip.framework.apollo.common.utils.InputValidator; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; @@ -33,6 +35,7 @@ @Table(name = "`AppNamespace`") @SQLDelete(sql = "Update AppNamespace set IsDeleted = true, DeletedAt = ROUND(UNIX_TIMESTAMP(NOW(4))*1000) where Id = ?") @Where(clause = "`IsDeleted` = false") +@ApolloAuditLogDataInfluenceTable(tableName = "AppNamespace") public class AppNamespace extends BaseEntity { @NotBlank(message = "AppNamespace Name cannot be blank") @@ -41,15 +44,19 @@ public class AppNamespace extends BaseEntity { message = "Invalid Namespace format: " + InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE + " & " + InputValidator.INVALID_NAMESPACE_NAMESPACE_MESSAGE ) @Column(name = "`Name`", nullable = false) + @ApolloAuditLogDataInfluenceTableField(fieldName = "Name") private String name; + @ApolloAuditLogDataInfluenceTableField(fieldName = "AppId") @NotBlank(message = "AppId cannot be blank") @Column(name = "`AppId`", nullable = false) private String appId; + @ApolloAuditLogDataInfluenceTableField(fieldName = "Format") @Column(name = "`Format`", nullable = false) private String format; + @ApolloAuditLogDataInfluenceTableField(fieldName = "IsPublic") @Column(name = "`IsPublic`", columnDefinition = "Bit default '0'") private boolean isPublic = false; diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/BaseEntity.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/BaseEntity.java index afc8720eb6c..38c1fc79bf3 100644 --- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/BaseEntity.java +++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/BaseEntity.java @@ -16,9 +16,12 @@ */ package com.ctrip.framework.apollo.common.entity; +import com.ctrip.framework.apollo.audit.event.ApolloAuditLogDataInfluenceEvent; import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects.ToStringHelper; +import java.util.Collection; +import java.util.Collections; import java.util.Date; import javax.persistence.Column; @@ -31,6 +34,7 @@ import javax.persistence.PrePersist; import javax.persistence.PreRemove; import javax.persistence.PreUpdate; +import org.springframework.data.domain.DomainEvents; @MappedSuperclass @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) @@ -148,4 +152,9 @@ protected ToStringHelper toStringHelper() { public String toString(){ return toStringHelper().toString(); } + + @DomainEvents + public Collection domainEvents() { + return Collections.singletonList(new ApolloAuditLogDataInfluenceEvent(this.getClass(), this)); + } } diff --git a/apollo-configservice/src/main/resources/application.properties b/apollo-configservice/src/main/resources/application.properties index 5ede28e5782..2e9aeb28c0b 100644 --- a/apollo-configservice/src/main/resources/application.properties +++ b/apollo-configservice/src/main/resources/application.properties @@ -21,4 +21,4 @@ #spring.profiles.active=github,database-discovery # You may change the following config to activate different database profiles like h2/postgres -spring.profiles.group.github = mysql +spring.profiles.group.github = mysql \ No newline at end of file diff --git a/apollo-portal/pom.xml b/apollo-portal/pom.xml index a22cf408ff8..f0b1ab1c681 100644 --- a/apollo-portal/pom.xml +++ b/apollo-portal/pom.xml @@ -44,6 +44,10 @@ com.ctrip.framework.apollo apollo-openapi + + com.ctrip.framework.apollo + apollo-audit-spring-boot-starter + org.springframework.boot spring-boot-starter-oauth2-client diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java index 5224237d803..fcd46a32fa1 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java @@ -16,6 +16,8 @@ */ package com.ctrip.framework.apollo.portal.api; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.annotation.OpType; import com.ctrip.framework.apollo.common.dto.*; import com.ctrip.framework.apollo.openapi.dto.OpenItemDTO; import com.ctrip.framework.apollo.portal.entity.po.ServerConfig; @@ -58,14 +60,17 @@ public AppDTO loadApp(Env env, String appId) { return restTemplate.get(env, "apps/{appId}", AppDTO.class, appId); } + @ApolloAuditLog(type = OpType.RPC, name = "App.createInRemote") public AppDTO createApp(Env env, AppDTO app) { return restTemplate.post(env, "apps", app, AppDTO.class); } + @ApolloAuditLog(type = OpType.RPC, name = "App.updateInRemote") public void updateApp(Env env, AppDTO app) { restTemplate.put(env, "apps/{appId}", app, app.getAppId()); } + @ApolloAuditLog(type = OpType.RPC, name = "App.deleteInRemote") public void deleteApp(Env env, String appId, String operator) { restTemplate.delete(env, "/apps/{appId}?operator={operator}", appId, operator); } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/audit/ApolloAuditLogQueryApiPortalPreAuthorizer.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/audit/ApolloAuditLogQueryApiPortalPreAuthorizer.java new file mode 100644 index 00000000000..99dc383db77 --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/audit/ApolloAuditLogQueryApiPortalPreAuthorizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.portal.audit; + +import com.ctrip.framework.apollo.audit.spi.ApolloAuditLogQueryApiPreAuthorizer; +import com.ctrip.framework.apollo.portal.component.PermissionValidator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +@Component("apolloAuditLogQueryApiPreAuthorizer") +@ConditionalOnProperty(prefix = "apollo.audit.log", name = "enabled", havingValue = "true") +public class ApolloAuditLogQueryApiPortalPreAuthorizer implements + ApolloAuditLogQueryApiPreAuthorizer { + private final PermissionValidator permissionValidator; + + public ApolloAuditLogQueryApiPortalPreAuthorizer(PermissionValidator permissionValidator) { + this.permissionValidator = permissionValidator; + } + + @Override + public boolean hasQueryPermission() { + return permissionValidator.isSuperAdmin(); + } +} diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/audit/ApolloAuditOperatorPortalSupplier.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/audit/ApolloAuditOperatorPortalSupplier.java new file mode 100644 index 00000000000..8edc4a2f972 --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/audit/ApolloAuditOperatorPortalSupplier.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.ctrip.framework.apollo.portal.audit; + +import com.ctrip.framework.apollo.audit.spi.ApolloAuditOperatorSupplier; +import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty(prefix = "apollo.audit.log", name = "enabled", havingValue = "true") +public class ApolloAuditOperatorPortalSupplier implements ApolloAuditOperatorSupplier { + + private final UserInfoHolder userInfoHolder; + + public ApolloAuditOperatorPortalSupplier(UserInfoHolder userInfoHolder) { + this.userInfoHolder = userInfoHolder; + } + + @Override + public String getOperator() { + return userInfoHolder.getUser().getName(); + } +} diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/RestTemplateFactory.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/RestTemplateFactory.java index 4d4c73e1d68..c3c63162e30 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/RestTemplateFactory.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/RestTemplateFactory.java @@ -16,6 +16,7 @@ */ package com.ctrip.framework.apollo.portal.component; +import com.ctrip.framework.apollo.audit.component.ApolloAuditHttpInterceptor; import com.ctrip.framework.apollo.portal.component.config.PortalConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -33,13 +34,15 @@ public class RestTemplateFactory implements FactoryBean, Initializ private final HttpMessageConverters httpMessageConverters; private final PortalConfig portalConfig; + private final ApolloAuditHttpInterceptor apolloAuditHttpInterceptor; private RestTemplate restTemplate; public RestTemplateFactory(final HttpMessageConverters httpMessageConverters, - final PortalConfig portalConfig) { + final PortalConfig portalConfig, final ApolloAuditHttpInterceptor apolloAuditHttpInterceptor) { this.httpMessageConverters = httpMessageConverters; this.portalConfig = portalConfig; + this.apolloAuditHttpInterceptor = apolloAuditHttpInterceptor; } public RestTemplate getObject() { @@ -64,6 +67,7 @@ public void afterPropertiesSet() throws UnsupportedEncodingException { requestFactory.setReadTimeout(portalConfig.readTimeout()); restTemplate.setRequestFactory(requestFactory); + restTemplate.getInterceptors().add(apolloAuditHttpInterceptor); } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AppController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AppController.java index a20393fc317..1a171b5bb50 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AppController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AppController.java @@ -17,6 +17,8 @@ package com.ctrip.framework.apollo.portal.controller; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.annotation.OpType; import com.ctrip.framework.apollo.common.dto.AppDTO; import com.ctrip.framework.apollo.common.entity.App; import com.ctrip.framework.apollo.common.exception.BadRequestException; @@ -119,6 +121,7 @@ public List findAppsByOwner(@RequestParam("owner") String owner, Pageable p @PreAuthorize(value = "@permissionValidator.hasCreateApplicationPermission()") @PostMapping + @ApolloAuditLog(type = OpType.CREATE) public App create(@Valid @RequestBody AppModel appModel) { App app = transformToApp(appModel); @@ -127,6 +130,7 @@ public App create(@Valid @RequestBody AppModel appModel) { @PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)") @PutMapping("/{appId:.+}") + @ApolloAuditLog(type = OpType.UPDATE) public void update(@PathVariable String appId, @Valid @RequestBody AppModel appModel) { if (!Objects.equals(appId, appModel.getAppId())) { throw new BadRequestException("The App Id of path variable and request body is different"); @@ -178,6 +182,7 @@ public AppDTO load(@PathVariable String appId) { @PreAuthorize(value = "@permissionValidator.isSuperAdmin()") @DeleteMapping("/{appId:.+}") + @ApolloAuditLog(type = OpType.RPC, name = "App.delete.request") public void deleteApp(@PathVariable String appId) { App app = appService.deleteAppInLocal(appId); diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppNamespaceService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppNamespaceService.java index 3a2b840baa1..5fdec7e796b 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppNamespaceService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppNamespaceService.java @@ -16,6 +16,11 @@ */ package com.ctrip.framework.apollo.portal.service; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluence; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTable; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluenceTableField; +import com.ctrip.framework.apollo.audit.annotation.OpType; import com.ctrip.framework.apollo.common.entity.App; import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.exception.BadRequestException; @@ -94,6 +99,7 @@ public List findAll() { return Lists.newArrayList(appNamespaces); } + @ApolloAuditLog(type = OpType.CREATE, name = "AppNamespace.create", description = "createDefaultAppNamespace") @Transactional public void createDefaultAppNamespace(String appId) { if (!isAppNamespaceNameUnique(appId, ConfigConsts.NAMESPACE_APPLICATION)) { @@ -124,6 +130,7 @@ public AppNamespace createAppNamespaceInLocal(AppNamespace appNamespace) { } @Transactional + @ApolloAuditLog(type = OpType.CREATE, name = "AppNamespace.create", description = "createAppNamespaceInLocal") public AppNamespace createAppNamespaceInLocal(AppNamespace appNamespace, boolean appendNamespacePrefix) { String appId = appNamespace.getAppId(); @@ -228,7 +235,7 @@ private void checkPublicAppNamespaceGlobalUniqueness(AppNamespace appNamespace) } } - + @ApolloAuditLog(type = OpType.DELETE, name = "AppNamespace.delete", description = "deleteAppNamespace") @Transactional public AppNamespace deleteAppNamespace(String appId, String namespaceName) { AppNamespace appNamespace = appNamespaceRepository.findByAppIdAndName(appId, namespaceName); @@ -250,7 +257,12 @@ public AppNamespace deleteAppNamespace(String appId, String namespaceName) { return appNamespace; } - public void batchDeleteByAppId(String appId, String operator) { + @ApolloAuditLog(type = OpType.DELETE, name = "AppNamespace.batchDeleteByAppId", description = "batchDeleteByAppId") + public void batchDeleteByAppId( + @ApolloAuditLogDataInfluence + @ApolloAuditLogDataInfluenceTable(tableName = "AppNamespace") + @ApolloAuditLogDataInfluenceTableField(fieldName = "AppId") String appId, + String operator) { appNamespaceRepository.batchDeleteByAppId(appId, operator); } diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppService.java index 9184d90a1a0..b314bdc1d84 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AppService.java @@ -16,6 +16,10 @@ */ package com.ctrip.framework.apollo.portal.service; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog; +import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLogDataInfluence; +import com.ctrip.framework.apollo.audit.annotation.OpType; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; import com.ctrip.framework.apollo.common.dto.AppDTO; import com.ctrip.framework.apollo.common.dto.PageDTO; import com.ctrip.framework.apollo.common.entity.App; @@ -35,6 +39,7 @@ import com.ctrip.framework.apollo.portal.util.RoleUtils; import com.ctrip.framework.apollo.tracer.Tracer; import com.google.common.collect.Lists; +import java.util.Collections; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -57,6 +62,7 @@ public class AppService { private final RolePermissionService rolePermissionService; private final FavoriteService favoriteService; private final UserService userService; + private final ApolloAuditLogApi apolloAuditLogApi; private final ApplicationEventPublisher publisher; @@ -69,7 +75,8 @@ public AppService( final RoleInitializationService roleInitializationService, final RolePermissionService rolePermissionService, final FavoriteService favoriteService, - final UserService userService, ApplicationEventPublisher publisher) { + final UserService userService, ApplicationEventPublisher publisher, + final ApolloAuditLogApi apolloAuditLogApi) { this.userInfoHolder = userInfoHolder; this.appAPI = appAPI; this.appRepository = appRepository; @@ -79,6 +86,7 @@ public AppService( this.rolePermissionService = rolePermissionService; this.favoriteService = favoriteService; this.userService = userService; + this.apolloAuditLogApi = apolloAuditLogApi; this.publisher = publisher; } @@ -161,6 +169,7 @@ private App createAppInLocal(App app) { } @Transactional + @ApolloAuditLog(type = OpType.CREATE, name = "App.create") public App createAppAndAddRolePermission( App app, Set admins ) { @@ -197,6 +206,7 @@ public App importAppInLocal(App app) { } @Transactional + @ApolloAuditLog(type = OpType.UPDATE, name = "App.update") public App updateAppInLocal(App app) { String appId = app.getAppId(); @@ -230,6 +240,7 @@ public EnvClusterInfo createEnvNavNode(Env env, String appId) { } @Transactional + @ApolloAuditLog(type = OpType.DELETE, name = "App.delete") public App deleteAppInLocal(String appId) { App managedApp = appRepository.findByAppId(appId); if (managedApp == null) { @@ -243,6 +254,9 @@ public App deleteAppInLocal(String appId) { //删除portal数据库中的app appRepository.deleteApp(appId, operator); + // append a deleted data influence should be bounded + apolloAuditLogApi.appendDataInfluences(Collections.singletonList(managedApp), App.class); + //删除portal数据库中的appNamespace appNamespaceService.batchDeleteByAppId(appId, operator); diff --git a/apollo-portal/src/main/resources/application.properties b/apollo-portal/src/main/resources/application.properties index 5ede28e5782..aa1bbde74bc 100644 --- a/apollo-portal/src/main/resources/application.properties +++ b/apollo-portal/src/main/resources/application.properties @@ -22,3 +22,7 @@ # You may change the following config to activate different database profiles like h2/postgres spring.profiles.group.github = mysql + +# true: enabled the new feature of audit log +# false/missing: disable it +apollo.audit.log.enabled = true diff --git a/apollo-portal/src/main/resources/static/audit_log_menu.html b/apollo-portal/src/main/resources/static/audit_log_menu.html new file mode 100644 index 00000000000..f48a6ff2ace --- /dev/null +++ b/apollo-portal/src/main/resources/static/audit_log_menu.html @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + {{'ApolloAuditLog.Title' | translate}} + + + + +
+
+
+
+

{{'ApolloAuditLog.Disabled' | translate}}

+
+

{{'ApolloAuditLog.DisabledTips' | translate}}

+

{{'ApolloAuditLog.MoreDetails' | translate}}

+
+
+
+
+
+
+
+ + +
+
+ + + + + + + + + + + + + + + +
{{'ApolloAuditLog.OpName' | translate }}{{'ApolloAuditLog.OpType' | translate }}{{'ApolloAuditLog.Operator' | translate }}{{'ApolloAuditLog.HappenedTime' | translate }}{{'ApolloAuditLog.Description' | translate }}
{{ alog.opName }}{{ alog.opType }}{{ alog.operator }}{{ alog.happenedTime | date: 'yyyy-MM-dd HH:mm:ss' }}{{ alog.description }}
+
+
+
+
+
+
{{'ApolloAuditLog.LoadMore' | translate }}
+
+
+
+
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apollo-portal/src/main/resources/static/audit_log_trace_detail.html b/apollo-portal/src/main/resources/static/audit_log_trace_detail.html new file mode 100644 index 00000000000..f66bc60b213 --- /dev/null +++ b/apollo-portal/src/main/resources/static/audit_log_trace_detail.html @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + {{ 'ApolloAuditLog.TraceDetailTips' | translate }} + + + + +
+
+
+ +
+

{{ 'ApolloAuditLog.TraceDetailTips' | translate }}

+ {{'ApolloAuditLog.TraceIdTips' | translate }}:{{traceId}} + +
+ +
+ +
+
+ +
+ + {{'ApolloAuditLog.TraceAuditLogTips' | translate }} +
+
+
+

+ {{detail.logDTO.opName}} + +

+
+
{{'ApolloAuditLog.SpanIdTips' | translate }}:{{detail.logDTO.spanId.substr(0,5)+'...'}}
+
{{'ApolloAuditLog.ParentSpan' | translate }}:{{findOpNameBySpanId(detail.logDTO.parentSpanId)}}
+
{{'ApolloAuditLog.FollowsFromSpan' | translate }}:{{findOpNameBySpanId(detail.logDTO.followsFromSpanId)}}
+
+
+
+
+ +
+
+ {{'ApolloAuditLog.RelatedDataInfluenceTips' | translate }} +
+
+
+
+

{{'ApolloAuditLog.OpType' | translate}}:{{showingDetail.logDTO.opType}}

+
+ {{'ApolloAuditLog.Operator' | translate}}:{{showingDetail.logDTO.operator}} +
+
+ {{'ApolloAuditLog.OpName' | translate}}:{{showingDetail.logDTO.opName}} +
+
+ {{'ApolloAuditLog.Description' | translate}}:{{showingDetail.logDTO.description}} +
+
+

+ {{'ApolloAuditLog.InfluenceEntity' | translate}}: +

+
+
+
+
+
+ {{'ApolloAuditLog.DataInfluence.EntityName' | translate}}: {{dataInfluenceEntity[0].name}} +
+
+ {{'ApolloAuditLog.DataInfluence.EntityId' | translate}}: {{dataInfluenceEntity[0].id}} +
+
+ {{'ApolloAuditLog.DataInfluence.AnyMatchedEntityId' | translate}} +
+
+
+
+ {{'ApolloAuditLog.DataInfluence.Fields' | translate}}: +
+
+ {{'ApolloAuditLog.DataInfluence.MatchedFields' | translate}}: +
+
+
+ {{dataInfluence.fieldName}} ==> {{dataInfluence.fieldNewValue}} +
+
+ {{dataInfluence.fieldName}} <== + {{showingDetail.logDTO.opType == 'DELETE' ? dataInfluence.fieldOldValue : dataInfluence.fieldNewValue}} +
+
+
+
+
+
{{'ApolloAuditLog.NoDataInfluence' | translate }}
+
+
+
+
+
+
+ {{'ApolloAuditLog.FieldChangeHistory' | translate }} +
+
+
+

+ {{entityNameOfFindRelated + ':' + entityIdOfFindRelated + ':' + fieldNameOfFindRelated}} +

+
+ + + + + + + + + + + + + +
{{'ApolloAuditLog.DataInfluence.FieldNewValue' | translate }}{{'ApolloAuditLog.DataInfluence.HappenedTime' | translate }}
{{ di.fieldNewValue }}{{ di.happenedTime | date: 'yyyy-MM-dd HH:mm:ss' }}
+
+
+ {{'ApolloAuditLog.DataInfluence.LoadMore' | translate }} +
+
+
{{'ApolloAuditLog.NoDataInfluence' | translate }}
+
+
+
+
+
+ +
+

+ {{'ApolloAuditLog.NoTraceDetail' | translate }}

+
+
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json index c20959c323c..4829d6cd11d 100644 --- a/apollo-portal/src/main/resources/static/i18n/en.json +++ b/apollo-portal/src/main/resources/static/i18n/en.json @@ -856,5 +856,40 @@ "Rollback.RollbackSuccessfully": "Rollback Successfully", "Rollback.RollbackFailed": "Failed to Rollback", "Revoke.RevokeFailed": "Failed to Revoke", - "Revoke.RevokeSuccessfully": "Revoke Successfully" + "Revoke.RevokeSuccessfully": "Revoke Successfully", + "ApolloAuditLog.Disabled": "Audit Log disabled already", + "ApolloAuditLog.DisabledTips": "you can add \"apollo.audit.log.enabled = true\" on properties to enable it", + "ApolloAuditLog.MoreDetails": "more detail please see the document", + "ApolloAuditLog.TraceAuditLogTips": "AuditLogs of Trace", + "ApolloAuditLog.RelatedDataInfluenceTips": "DataInfluences of AuditLog", + "ApolloAuditLog.DataInfluenceTips": "DataInfluences:", + "ApolloAuditLog.DataInfluence.EntityName": "entity name", + "ApolloAuditLog.DataInfluence.EntityId": "entity ID", + "ApolloAuditLog.DataInfluence.AnyMatchedEntityId": "any entity matched", + "ApolloAuditLog.DataInfluence.FieldName": "field name", + "ApolloAuditLog.DataInfluence.FieldNewValue": "recorded value", + "ApolloAuditLog.DataInfluence.Fields": "influenced fields", + "ApolloAuditLog.DataInfluence.MatchedFields": "matched fields", + "ApolloAuditLog.DataInfluence.HappenedTime": "recorded time", + "ApolloAuditLog.TraceIdTips": "Trace Unique ID", + "ApolloAuditLog.OpName": "operate name", + "ApolloAuditLog.HappenedTime": "happened time", + "ApolloAuditLog.StartDate": "start date", + "ApolloAuditLog.Description": "description", + "ApolloAuditLog.Title": "Audit Log", + "ApolloAuditLog.DoQuery": "Query", + "ApolloAuditLog.OpNameTips": "type in operate name", + "ApolloAuditLog.OpType": "operate type", + "ApolloAuditLog.Operator": "operator", + "ApolloAuditLog.LoadMore": "load more", + "ApolloAuditLog.DataInfluence.LoadMore": "load more", + "ApolloAuditLog.EndDate": "end date", + "ApolloAuditLog.NoTraceDetail": "No Trace Details", + "ApolloAuditLog.NoDataInfluence": "No DataInfluences", + "ApolloAuditLog.TraceDetailTips": "Trace Details", + "ApolloAuditLog.SpanIdTips": "operation ID", + "ApolloAuditLog.ParentSpan": "parent operation", + "ApolloAuditLog.FollowsFromSpan": "last operation", + "ApolloAuditLog.FieldChangeHistory": "Field Change History", + "ApolloAuditLog.InfluenceEntity": "Audit entity influenced" } diff --git a/apollo-portal/src/main/resources/static/i18n/zh-CN.json b/apollo-portal/src/main/resources/static/i18n/zh-CN.json index da9c856b4e6..df84bf07269 100644 --- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json +++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json @@ -856,5 +856,40 @@ "Rollback.RollbackSuccessfully": "回滚成功", "Rollback.RollbackFailed": "回滚失败", "Revoke.RevokeFailed": "撤销失败", - "Revoke.RevokeSuccessfully": "撤销成功" + "Revoke.RevokeSuccessfully": "撤销成功", + "ApolloAuditLog.Disabled": "审计日志功能已关闭", + "ApolloAuditLog.DisabledTips": "你可以通过添加\"apollo.audit.log.enabled = true\"到配置文件以打开审计日志功能", + "ApolloAuditLog.MoreDetails": "更多细节请参考文档", + "ApolloAuditLog.TraceAuditLogTips": "链路包含审计日志", + "ApolloAuditLog.RelatedDataInfluenceTips": "审计日志相关数据变动", + "ApolloAuditLog.DataInfluenceTips": "数据变动展示", + "ApolloAuditLog.DataInfluence.EntityName": "实体名称", + "ApolloAuditLog.DataInfluence.EntityId": "实体ID", + "ApolloAuditLog.DataInfluence.AnyMatchedEntityId": "与条件匹配的实体", + "ApolloAuditLog.DataInfluence.FieldName": "实体属性名", + "ApolloAuditLog.DataInfluence.FieldNewValue": "属性值记录", + "ApolloAuditLog.DataInfluence.Fields": "变化字段", + "ApolloAuditLog.DataInfluence.MatchedFields": "匹配字段", + "ApolloAuditLog.DataInfluence.HappenedTime": "记录时间", + "ApolloAuditLog.TraceIdTips": "链路唯一ID", + "ApolloAuditLog.OpName": "操作名称", + "ApolloAuditLog.HappenedTime": "发生时间", + "ApolloAuditLog.Description": "操作备注", + "ApolloAuditLog.StartDate": "开始时间", + "ApolloAuditLog.Title": "审计日志", + "ApolloAuditLog.DoQuery": "查询", + "ApolloAuditLog.OpNameTips": "输入操作名称", + "ApolloAuditLog.OpType": "操作类型", + "ApolloAuditLog.Operator": "操作人", + "ApolloAuditLog.LoadMore": "加载更多", + "ApolloAuditLog.DataInfluence.LoadMore": "加载更多", + "ApolloAuditLog.EndDate": "结束时间", + "ApolloAuditLog.NoTraceDetail": "没有链路记录", + "ApolloAuditLog.NoDataInfluence": "没有相关数据变动", + "ApolloAuditLog.TraceDetailTips": "链路记录展示", + "ApolloAuditLog.SpanIdTips": "操作ID", + "ApolloAuditLog.ParentSpan": "父操作", + "ApolloAuditLog.FollowsFromSpan": "前操作", + "ApolloAuditLog.FieldChangeHistory": "属性变动历史", + "ApolloAuditLog.InfluenceEntity": "影响的审计实体" } diff --git a/apollo-portal/src/main/resources/static/scripts/app.js b/apollo-portal/src/main/resources/static/scripts/app.js index 1eccd9ef697..dd41eff155c 100644 --- a/apollo-portal/src/main/resources/static/scripts/app.js +++ b/apollo-portal/src/main/resources/static/scripts/app.js @@ -85,4 +85,8 @@ var system_info_module = angular.module('system_info', ['app.service', 'apollo.d //access secretKey var access_key_module = angular.module('access_key', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar']); //config export -var config_export_module = angular.module('config_export', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar']); \ No newline at end of file +var config_export_module = angular.module('config_export', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar']); +//audit log menu +var audit_log_menu_module = angular.module('audit_log', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar']); +//audit log trace detail +var audit_log_trace_detail_module = angular.module('audit_log_trace_detail', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar']); diff --git a/apollo-portal/src/main/resources/static/scripts/controller/AuditLogMenuController.js b/apollo-portal/src/main/resources/static/scripts/controller/AuditLogMenuController.js new file mode 100644 index 00000000000..d831655b944 --- /dev/null +++ b/apollo-portal/src/main/resources/static/scripts/controller/AuditLogMenuController.js @@ -0,0 +1,176 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +audit_log_menu_module.controller('AuditLogMenuController', + ['$scope', '$window', '$translate', '$document', 'toastr', 'AppService', 'AppUtil', 'EventManager', 'AuditLogService', + auditLogMenuController] +); + +function auditLogMenuController($scope, $window, $translate, $document, toastr, AppService, AppUtil, EventManager, AuditLogService) { + + $scope.auditEnabled = false; + + $scope.auditLogList = []; + $scope.goToTraceDetailsPage = goToTraceDetailsPage; + $scope.searchByOpNameAndDate = searchByOpNameAndDate; + $scope.getMoreAuditLogs = getMoreAuditLogs; + + $scope.page = 0; + var PAGE_SIZE = 10; + + $scope.opName = ''; + $scope.startDate = null; + $scope.endDate = null; + $scope.startDateFmt = null; + $scope.endDateFmt = null; + + $scope.hasLoadAll = false; + + $scope.options = []; + $scope.showSearchDropdown = false; + + $scope.showOptions = function(query) { + $scope.options = []; + searchAuditLogs(query); + }; + + $scope.selectOption = function(option) { + $scope.opName = option.opName; + $scope.showSearchDropdown = false; + }; + + init(); + + function init() { + getAuditProperties(); + initSearchingMenu(); + } + + function getAuditProperties() { + AuditLogService.get_properties().then(function (result) { + $scope.auditEnabled = result.enabled; + }); + } + + function initSearchingMenu() { + AuditLogService.find_all_logs($scope.page, PAGE_SIZE).then(function (result) { + if (!result || result.length < PAGE_SIZE) { + $scope.hasLoadAll = true; + } + if (result.length === 0) { + return; + } + $scope.auditLogList = $scope.auditLogList.concat(result); + }); + } + + function searchByOpNameAndDate(opName, startDate, endDate) { + if (startDate !== null) { + $scope.startDateFmt = new Date(startDate).Format("yyyy-MM-dd hh:mm:ss.S"); + } + if (endDate !== null) { + $scope.endDateFmt = new Date(endDate).Format("yyyy-MM-dd hh:mm:ss.S"); + } + $scope.auditLogList = []; + $scope.page = 0; + $scope.opName = opName; + $scope.startDate = startDate; + $scope.endDate = endDate; + AuditLogService.find_logs_by_opName( + $scope.opName, + $scope.startDateFmt, + $scope.endDateFmt, + $scope.page, + PAGE_SIZE + ).then(function (result) { + if (!result || result.length < PAGE_SIZE) { + $scope.hasLoadAll = true; + } + if (result.length === 0) { + return; + } + $scope.auditLogList = $scope.auditLogList.concat(result); + }); + } + + function getMoreAuditLogs() { + $scope.page = $scope.page + 1; + if($scope.opName === '') { + AuditLogService.find_all_logs($scope.page, PAGE_SIZE).then(function (result) { + if (!result || result.length < PAGE_SIZE) { + $scope.hasLoadAll = true; + } + if (result.length === 0) { + return; + } + $scope.auditLogList = $scope.auditLogList.concat(result); + }); + }else { + AuditLogService.find_logs_by_opName( + $scope.opName, + $scope.startDateFmt, + $scope.endDateFmt, + $scope.page, + PAGE_SIZE + ).then(function (result) { + if (!result || result.length < PAGE_SIZE) { + $scope.hasLoadAll = true; + } + if (result.length === 0) { + return; + } + $scope.auditLogList = $scope.auditLogList.concat(result); + }); + } + } + + function searchAuditLogs(query) { + AuditLogService.search_by_name_or_type_or_operator(query, 0, 20).then(function (result) { + result.forEach(function (log) { + var optionDisplay = log.opName + '-(' + log.opType + ').by:' + log.operator; + var option = { + id: log.id, + display: optionDisplay, + opName: log.opName + }; + $scope.options.push(option); + }); + $scope.showSearchDropdown = $scope.options.length > 0; + }); + } + + function goToTraceDetailsPage(traceId) { + $window.location.href = AppUtil.prefixPath() + "/audit_log_trace_detail.html?#traceId=" + traceId; + } + + $document.on('click', function(event) { + if (!$scope.showSearchDropdown) { + return; + } + + var target = angular.element(event.target); + + // 检查点击的目标是否是输入框或下拉栏,如果不是,则隐藏下拉栏 + if (!target.hasClass('form-control') && !target.hasClass('options-container')) { + $scope.$apply(function() { + $scope.showSearchDropdown = false; + }); + } + }); +} + + + diff --git a/apollo-portal/src/main/resources/static/scripts/controller/AuditLogTraceDetailController.js b/apollo-portal/src/main/resources/static/scripts/controller/AuditLogTraceDetailController.js new file mode 100644 index 00000000000..43b56f196a1 --- /dev/null +++ b/apollo-portal/src/main/resources/static/scripts/controller/AuditLogTraceDetailController.js @@ -0,0 +1,151 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +audit_log_trace_detail_module.controller('AuditLogTraceDetailController', + ['$scope', '$location', '$window', '$translate', 'toastr', 'AppService', 'AppUtil', 'EventManager', 'AuditLogService', + auditLogTraceDetailController] +); +function auditLogTraceDetailController($scope, $location, $window, $translate, toastr, AppService, AppUtil, EventManager, AuditLogService) { + var params = AppUtil.parseParams($location.$$url); + $scope.traceId = params.traceId; + + $scope.traceDetails = []; + $scope.showingDetail = {}; + $scope.dataInfluenceEntities = []; + $scope.relatedDataInfluences = []; + $scope.relatedDataInfluencePage = 0; + $scope.relatedDataInfluenceHasLoadAll = true; + var RelatedDataInfluencePageSize = 10; + $scope.setShowingDetail = setShowingDetail; + $scope.showText = showText; + $scope.removeInClassFromLogDropDownExceptId = removeInClassFromLogDropDownExceptId; + $scope.findMoreRelatedDataInfluence = findMoreRelatedDataInfluence; + $scope.showRelatedDataInfluence = showRelatedDataInfluence; + $scope.findOpNameBySpanId = findOpNameBySpanId; + $scope.refreshDataInfluenceEntities = refreshDataInfluenceEntities; + + init(); + + function init() { + getTraceDetails(); + } + + function getTraceDetails() { + AuditLogService.find_trace_details($scope.traceId).then( + function (result) { + $scope.traceDetails = result; + } + ); + } + + function setShowingDetail(detail) { + $scope.showingDetail = detail; + refreshDataInfluenceEntities(); + } + + function removeInClassFromLogDropDownExceptId(id) { + $scope.relatedDataInfluences = []; + $scope.relatedDataInfluenceHasLoadAll = true; + + var elements = document.querySelectorAll('[id^="detail"]'); + + elements.forEach(function (element) { + if(element.id !== 'detail'+id) { + element.classList.remove('in'); + } + + }); + } + + function showRelatedDataInfluence(entityName, entityId, fieldName) { + $scope.entityNameOfFindRelated = entityName; + $scope.entityIdOfFindRelated = entityId; + $scope.fieldNameOfFindRelated = fieldName; + + if (entityId === 'AnyMatched') { + return; + } + + AuditLogService.find_dataInfluences_by_field( + $scope.entityNameOfFindRelated, + $scope.entityIdOfFindRelated, + $scope.fieldNameOfFindRelated, + $scope.relatedDataInfluencePage, + RelatedDataInfluencePageSize + ).then(function (result) { + if (!result || result.length < RelatedDataInfluencePageSize) { + $scope.relatedDataInfluenceHasLoadAll = true; + $scope.relatedDataInfluences = result; + return; + } + if (result.length === 0) { + return; + } + $scope.relatedDataInfluenceHasLoadAll = false; + $scope.relatedDataInfluences = result; + }); + } + + function findMoreRelatedDataInfluence() { + $scope.relatedDataInfluencePage = $scope.relatedDataInfluencePage + 1; + AuditLogService.find_dataInfluences_by_field( + $scope.entityNameOfFindRelated, + $scope.entityIdOfFindRelated, + $scope.fieldNameOfFindRelated, + $scope.relatedDataInfluencePage, + RelatedDataInfluencePageSize + ).then(function (result) { + if (!result || result.length < RelatedDataInfluencePageSize) { + $scope.relatedDataInfluenceHasLoadAll = true; + } + if (result.length === 0) { + return; + } + $scope.relatedDataInfluences = $scope.relatedDataInfluences.concat(result); + + }); + } + + function findOpNameBySpanId(spanId) { + var res = ''; + $scope.traceDetails.forEach(function (detail) { + if (detail.logDTO.spanId === spanId) { + res = detail.logDTO.opName +'('+spanId.substr(0,5)+'...'+')'; + } + }); + return res; + } + + function refreshDataInfluenceEntities() { + var entityMap = new Map(); + $scope.showingDetail.dataInfluenceDTOList.forEach(function (dto) { + var key = { + name: dto.influenceEntityName, + id: dto.influenceEntityId + }; + if (!entityMap.has(key)) { + entityMap.set(key, []); + } + entityMap.get(key).push(dto); + }); + $scope.dataInfluenceEntities = Array.from(entityMap); + } + + function showText(text) { + $scope.text = text; + AppUtil.showModal("#showTextModal"); + } +} \ No newline at end of file diff --git a/apollo-portal/src/main/resources/static/scripts/services/AuditLogService.js b/apollo-portal/src/main/resources/static/scripts/services/AuditLogService.js new file mode 100644 index 00000000000..a9a07ded722 --- /dev/null +++ b/apollo-portal/src/main/resources/static/scripts/services/AuditLogService.js @@ -0,0 +1,134 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +appService.service('AuditLogService', ['$resource', '$q', 'AppUtil', function ($resource, $q, AppUtil) { + var audit_resource = $resource('', {}, { + get_properties: { + method: 'GET', + url: AppUtil.prefixPath() + '/apollo/audit/properties', + isArray: false + }, + find_all_logs: { + method: 'GET', + url: AppUtil.prefixPath() + '/apollo/audit/logs?page=:page&size=:size', + isArray: true + }, + find_logs_by_opName: { + method: 'GET', + url: AppUtil.prefixPath() + '/apollo/audit/logs/opName?opName=:opName&page=:page&size=:size&startDate=:startDate&endDate=:endDate', + isArray: true + }, + find_trace_details: { + method: 'GET', + url: AppUtil.prefixPath() + '/apollo/audit/trace?traceId=:traceId', + isArray: true + }, + find_dataInfluences_by_field: { + method: 'GET', + url: AppUtil.prefixPath() + '/apollo/audit/logs/dataInfluences/field?entityName=:entityName&entityId=:entityId&fieldName=:fieldName&page=:page&size=:size', + isArray: true + }, + search_by_name_or_type_or_operator: { + method: 'GET', + url: AppUtil.prefixPath() + '/apollo/audit/logs/by-name-or-type-or-operator?query=:query&page=:page&size=:size', + isArray: true + } + }); + return { + get_properties: function () { + var d = $q.defer(); + audit_resource.get_properties({ + }, function (result) { + d.resolve(result); + }, function (result) { + d.reject(result); + } + ); + return d.promise; + }, + find_all_logs: function (page, size) { + var d = $q.defer(); + audit_resource.find_all_logs({ + page: page, + size: size + }, function (result) { + d.resolve(result); + }, function (result) { + d.reject(result); + } + ); + return d.promise; + }, + find_logs_by_opName: function (opName, startDate, endDate, page, size) { + var d = $q.defer(); + audit_resource.find_logs_by_opName({ + opName: opName, + startDate: startDate, + endDate: endDate, + page: page, + size: size + }, function (result) { + d.resolve(result); + }, function (result) { + d.reject(result); + } + ); + return d.promise; + }, + find_trace_details: function (traceId) { + var d = $q.defer(); + audit_resource.find_trace_details({ + traceId: traceId + }, function (result) { + d.resolve(result); + }, function (result) { + d.reject(result); + } + ); + return d.promise; + }, + find_dataInfluences_by_field: function (entityName, entityId, fieldName, page, size) { + var d = $q.defer(); + audit_resource.find_dataInfluences_by_field({ + entityName: entityName, + entityId: entityId, + fieldName: fieldName, + page: page, + size: size + }, function (result) { + d.resolve(result); + }, function (result) { + d.reject(result); + } + ); + return d.promise; + }, + search_by_name_or_type_or_operator: function (query, page, size) { + var d = $q.defer(); + audit_resource.search_by_name_or_type_or_operator({ + query: query, + page: page, + size: size + }, function (result) { + d.resolve(result); + }, function (result) { + d.reject(result); + } + ); + return d.promise; + } + }; +}]); \ No newline at end of file diff --git a/apollo-portal/src/main/resources/static/styles/audit-log.css b/apollo-portal/src/main/resources/static/styles/audit-log.css new file mode 100644 index 00000000000..8d46d9cd33b --- /dev/null +++ b/apollo-portal/src/main/resources/static/styles/audit-log.css @@ -0,0 +1,140 @@ +/* + * Copyright 2023 Apollo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#audit-not-enabled-prompt { + background-color: #ffffff; + /*实现垂直居中*/ + align-items: center; + /*实现水平居中*/ + justify-content: center; +} + +#audit-not-enabled-prompt .display-text { + padding: 15px; + margin-left: 30px; + margin-right: 30px; + box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .09); +} + + +/*audit-menu page*/ +#audit-menu h5 { + word-wrap: break-word; + word-break: break-all; +} + +#audit-menu { + background-color: #ffffff; + /*实现垂直居中*/ + align-items: center; + /*实现水平居中*/ + justify-content: center; + +} + +#audit-menu .main-table { + padding-top: 15px; + width: 100%; + min-width: 500px; +} + +#audit-menu .more-img { + width: 20px; + height: 20px; +} + +#audit-menu .operate-panel { + position: absolute; + top: 5px; + right: 20px; +} + +#audit-menu .display-table { + padding: 15px; + margin-left: 30px; + margin-right: 30px; + box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .09); +} + +#audit-menu .search-bar { + padding: 15px; + margin-left: 30px; + margin-right: 30px; + box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .09); +} + +#audit-menu table { + width: 100%; +} + +#audit-menu tr { +} + +#audit-menu th { + border-bottom: 1px solid #E4E7ED; + padding: 15px 15px 15px 15px; + font-size: 16px; + text-align: left; + word-break: break-all; +} + +#audit-menu td { + border-bottom: 1px solid #E4E7ED; + padding: 15px 15px 15px 15px; + font-size: 14px; + word-break: break-all; +} +.data-influence { + display: table-row; + padding: 8px 8px 8px 8px; + font-size: 16px; + color: #292a29; +} +.audit-field:hover{ + background-color: #f5f5f5; +} + +#dataInfluenceEntity { + padding: 8px; + margin-top: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .09); + border-radius: 3%; +} + +.options-container { + max-height: 150px; /* 设置最大高度,可根据需求调整 */ + overflow-y: auto; /* 启用垂直滚动条 */ + border: 1px solid #ccc; /* 添加边框,可根据需要自定义样式 */ + border-top: 0; /* 避免重叠边框 */ + background: #fff; /* 背景颜色,可根据需要调整 */ + z-index: 1000; +} + +.options-list { + list-style: none; + padding: 0; + margin: 0; +} + +.options-list li { + padding: 5px; + cursor: pointer; +} + +.options-list li:hover { + background-color: #f0f0f0; +} \ No newline at end of file diff --git a/apollo-portal/src/main/resources/static/views/common/nav.html b/apollo-portal/src/main/resources/static/views/common/nav.html index 4a81913a34d..6d57f99777b 100644 --- a/apollo-portal/src/main/resources/static/views/common/nav.html +++ b/apollo-portal/src/main/resources/static/views/common/nav.html @@ -65,6 +65,7 @@
  • {{'Common.Nav.DeleteApp-Cluster-Namespace' | translate }}
  • {{'Common.Nav.SystemInfo' | translate }}
  • {{'Common.Nav.ConfigExport' | translate }}
  • +
  • {{'ApolloAuditLog.Title' | translate }}
  • diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/AppServiceTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/AppServiceTest.java index f9454b29bdc..eafa5fd6bb5 100644 --- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/AppServiceTest.java +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/AppServiceTest.java @@ -16,8 +16,10 @@ */ package com.ctrip.framework.apollo.portal.service; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.ctrip.framework.apollo.audit.api.ApolloAuditLogApi; import com.ctrip.framework.apollo.common.entity.App; import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; @@ -73,6 +75,8 @@ class AppServiceTest { UserService userService; @MockBean ApplicationEventPublisher publisher; + @MockBean + ApolloAuditLogApi apolloAuditLogApi; @BeforeEach void beforeEach() { @@ -87,7 +91,8 @@ void beforeEach() { rolePermissionService, favoriteService, userService, - publisher + publisher, + apolloAuditLogApi ); UserInfo userInfo = new UserInfo(); userInfo.setUserId(OPERATOR_USER_ID); @@ -157,4 +162,35 @@ void createAppAndAddRolePermission() { Mockito.verify(rolePermissionService, Mockito.times(1)) .assignRoleToUsers(Mockito.any(), Mockito.eq(admins), Mockito.eq(OPERATOR_USER_ID)); } + + @Test + void testDeleteAppInLocal() { + final String appId = "appId100"; + { + App app = new App(); + app.setAppId(appId); + Mockito.when(appRepository.findByAppId(Mockito.eq(appId))) + .thenReturn(app); + } + { + Mockito.when(appRepository.deleteApp(Mockito.eq(appId), Mockito.eq(OPERATOR_USER_ID))) + .thenReturn(1); + } + + App deletedApp = appService.deleteAppInLocal(appId); + Mockito.verify(appRepository, Mockito.times(1)) + .deleteApp(Mockito.eq(appId), Mockito.eq(OPERATOR_USER_ID)); + Mockito.verify(userInfoHolder, Mockito.times(1)) + .getUser(); + Mockito.verify(apolloAuditLogApi, Mockito.times(1)) + .appendDataInfluences(Mockito.eq(Collections.singletonList(deletedApp)), Mockito.eq(App.class)); + Mockito.verify(appNamespaceService, Mockito.times(1)) + .batchDeleteByAppId(Mockito.eq(appId), Mockito.eq(OPERATOR_USER_ID)); + Mockito.verify(favoriteService, Mockito.times(1)) + .batchDeleteByAppId(Mockito.eq(appId), Mockito.eq(OPERATOR_USER_ID)); + Mockito.verify(rolePermissionService, Mockito.times(1)) + .deleteRolePermissionsByAppId(Mockito.eq(appId), Mockito.eq(OPERATOR_USER_ID)); + + assertEquals(OPERATOR_USER_ID, deletedApp.getDataChangeLastModifiedBy()); + } } \ No newline at end of file diff --git a/pom.xml b/pom.xml index ef7daaa3b91..5c7de5fad63 100644 --- a/pom.xml +++ b/pom.xml @@ -102,7 +102,8 @@ apollo-adminservice apollo-portal apollo-assembly - + apollo-audit + @@ -146,6 +147,27 @@ apollo-openapi ${apollo-java.version}
    + + + com.ctrip.framework.apollo + apollo-audit-annotation + ${project.version} + + + com.ctrip.framework.apollo + apollo-audit-impl + ${project.version} + + + com.ctrip.framework.apollo + apollo-audit-api + ${project.version} + + + com.ctrip.framework.apollo + apollo-audit-spring-boot-starter + ${project.version} + com.google.guava diff --git a/scripts/sql/apolloconfigdb.sql b/scripts/sql/apolloconfigdb.sql index 40f6c81a54f..51f89640bed 100644 --- a/scripts/sql/apolloconfigdb.sql +++ b/scripts/sql/apolloconfigdb.sql @@ -446,3 +446,52 @@ VALUES /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + + +DROP TABLE IF EXISTS `AuditLog`; + +CREATE TABLE `AuditLog` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `TraceId` varchar(32) NOT NULL DEFAULT '' COMMENT '链路全局唯一ID', + `SpanId` varchar(32) NOT NULL DEFAULT '' COMMENT '跨度ID', + `ParentSpanId` varchar(32) DEFAULT NULL COMMENT '父跨度ID', + `FollowsFromSpanId` varchar(32) DEFAULT NULL COMMENT '上一个兄弟跨度ID', + `Operator` varchar(64) NOT NULL DEFAULT 'anonymous' COMMENT '操作人', + `OpType` varchar(50) NOT NULL DEFAULT 'default' COMMENT '操作类型', + `OpName` varchar(150) NOT NULL DEFAULT 'default' COMMENT '操作名称', + `Description` varchar(200) DEFAULT NULL COMMENT '备注', + `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', + `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds', + `DataChange_CreatedBy` varchar(64) DEFAULT NULL COMMENT '创建人邮箱前缀', + `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `DataChange_LastModifiedBy` varchar(64) DEFAULT '' COMMENT '最后修改人邮箱前缀', + `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + PRIMARY KEY (`Id`), + KEY `IX_TraceId` (`TraceId`), + KEY `IX_OpName` (`OpName`), + KEY `IX_DataChange_CreatedTime` (`DataChange_CreatedTime`), + KEY `IX_Operator` (`Operator`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志表'; + + +DROP TABLE IF EXISTS `AuditLogDataInfluence`; + +CREATE TABLE `AuditLogDataInfluence` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `SpanId` char(32) NOT NULL DEFAULT '' COMMENT '跨度ID', + `InfluenceEntityId` varchar(50) NOT NULL DEFAULT '0' COMMENT '记录ID', + `InfluenceEntityName` varchar(50) NOT NULL DEFAULT 'default' COMMENT '表名', + `FieldName` varchar(50) DEFAULT NULL COMMENT '字段名称', + `FieldOldValue` varchar(500) DEFAULT NULL COMMENT '字段旧值', + `FieldNewValue` varchar(500) DEFAULT NULL COMMENT '字段新值', + `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', + `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds', + `DataChange_CreatedBy` varchar(64) DEFAULT NULL COMMENT '创建人邮箱前缀', + `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `DataChange_LastModifiedBy` varchar(64) DEFAULT '' COMMENT '最后修改人邮箱前缀', + `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + PRIMARY KEY (`Id`), + KEY `IX_SpanId` (`SpanId`), + KEY `IX_DataChange_CreatedTime` (`DataChange_CreatedTime`), + KEY `IX_EntityId` (`InfluenceEntityId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志数据变动表'; \ No newline at end of file diff --git a/scripts/sql/apolloportaldb.sql b/scripts/sql/apolloportaldb.sql index a006b5362df..d0f0d0974bd 100644 --- a/scripts/sql/apolloportaldb.sql +++ b/scripts/sql/apolloportaldb.sql @@ -381,3 +381,52 @@ CREATE TABLE SPRING_SESSION_ATTRIBUTES ( /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; + + +DROP TABLE IF EXISTS `AuditLog`; + +CREATE TABLE `AuditLog` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `TraceId` varchar(32) NOT NULL DEFAULT '' COMMENT '链路全局唯一ID', + `SpanId` varchar(32) NOT NULL DEFAULT '' COMMENT '跨度ID', + `ParentSpanId` varchar(32) DEFAULT NULL COMMENT '父跨度ID', + `FollowsFromSpanId` varchar(32) DEFAULT NULL COMMENT '上一个兄弟跨度ID', + `Operator` varchar(64) NOT NULL DEFAULT 'anonymous' COMMENT '操作人', + `OpType` varchar(50) NOT NULL DEFAULT 'default' COMMENT '操作类型', + `OpName` varchar(150) NOT NULL DEFAULT 'default' COMMENT '操作名称', + `Description` varchar(200) DEFAULT NULL COMMENT '备注', + `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', + `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds', + `DataChange_CreatedBy` varchar(64) DEFAULT NULL COMMENT '创建人邮箱前缀', + `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `DataChange_LastModifiedBy` varchar(64) DEFAULT '' COMMENT '最后修改人邮箱前缀', + `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + PRIMARY KEY (`Id`), + KEY `IX_TraceId` (`TraceId`), + KEY `IX_OpName` (`OpName`), + KEY `IX_DataChange_CreatedTime` (`DataChange_CreatedTime`), + KEY `IX_Operator` (`Operator`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志表'; + + +DROP TABLE IF EXISTS `AuditLogDataInfluence`; + +CREATE TABLE `AuditLogDataInfluence` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `SpanId` char(32) NOT NULL DEFAULT '' COMMENT '跨度ID', + `InfluenceEntityId` varchar(50) NOT NULL DEFAULT '0' COMMENT '记录ID', + `InfluenceEntityName` varchar(50) NOT NULL DEFAULT 'default' COMMENT '表名', + `FieldName` varchar(50) DEFAULT NULL COMMENT '字段名称', + `FieldOldValue` varchar(500) DEFAULT NULL COMMENT '字段旧值', + `FieldNewValue` varchar(500) DEFAULT NULL COMMENT '字段新值', + `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', + `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds', + `DataChange_CreatedBy` varchar(64) DEFAULT NULL COMMENT '创建人邮箱前缀', + `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `DataChange_LastModifiedBy` varchar(64) DEFAULT '' COMMENT '最后修改人邮箱前缀', + `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + PRIMARY KEY (`Id`), + KEY `IX_SpanId` (`SpanId`), + KEY `IX_DataChange_CreatedTime` (`DataChange_CreatedTime`), + KEY `IX_EntityId` (`InfluenceEntityId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志数据变动表'; \ No newline at end of file diff --git a/scripts/sql/delta/v210-v220/apolloconfigdb-v210-v220.sql b/scripts/sql/delta/v210-v220/apolloconfigdb-v210-v220.sql index 3021a270f26..598ebf037fa 100644 --- a/scripts/sql/delta/v210-v220/apolloconfigdb-v210-v220.sql +++ b/scripts/sql/delta/v210-v220/apolloconfigdb-v210-v220.sql @@ -42,4 +42,52 @@ ALTER TABLE `Namespace` ALTER TABLE `Release` DROP INDEX `AppId_ClusterName_GroupName`, - ADD INDEX `AppId_ClusterName_GroupName` (`AppId`,`ClusterName`(191),`NamespaceName`(191),`DeletedAt`); \ No newline at end of file + ADD INDEX `AppId_ClusterName_GroupName` (`AppId`,`ClusterName`(191),`NamespaceName`(191),`DeletedAt`); + +DROP TABLE IF EXISTS `AuditLog`; + +CREATE TABLE `AuditLog` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `TraceId` varchar(32) NOT NULL DEFAULT '' COMMENT '链路全局唯一ID', + `SpanId` varchar(32) NOT NULL DEFAULT '' COMMENT '跨度ID', + `ParentSpanId` varchar(32) DEFAULT NULL COMMENT '父跨度ID', + `FollowsFromSpanId` varchar(32) DEFAULT NULL COMMENT '上一个兄弟跨度ID', + `Operator` varchar(64) NOT NULL DEFAULT 'anonymous' COMMENT '操作人', + `OpType` varchar(50) NOT NULL DEFAULT 'default' COMMENT '操作类型', + `OpName` varchar(150) NOT NULL DEFAULT 'default' COMMENT '操作名称', + `Description` varchar(200) DEFAULT NULL COMMENT '备注', + `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', + `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds', + `DataChange_CreatedBy` varchar(64) DEFAULT NULL COMMENT '创建人邮箱前缀', + `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `DataChange_LastModifiedBy` varchar(64) DEFAULT '' COMMENT '最后修改人邮箱前缀', + `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + PRIMARY KEY (`Id`), + KEY `IX_TraceId` (`TraceId`), + KEY `IX_OpName` (`OpName`), + KEY `IX_DataChange_CreatedTime` (`DataChange_CreatedTime`), + KEY `IX_Operator` (`Operator`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志表'; + + +DROP TABLE IF EXISTS `AuditLogDataInfluence`; + +CREATE TABLE `AuditLogDataInfluence` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `SpanId` char(32) NOT NULL DEFAULT '' COMMENT '跨度ID', + `InfluenceEntityId` varchar(50) NOT NULL DEFAULT '0' COMMENT '记录ID', + `InfluenceEntityName` varchar(50) NOT NULL DEFAULT 'default' COMMENT '表名', + `FieldName` varchar(50) DEFAULT NULL COMMENT '字段名称', + `FieldOldValue` varchar(500) DEFAULT NULL COMMENT '字段旧值', + `FieldNewValue` varchar(500) DEFAULT NULL COMMENT '字段新值', + `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', + `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds', + `DataChange_CreatedBy` varchar(64) DEFAULT NULL COMMENT '创建人邮箱前缀', + `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `DataChange_LastModifiedBy` varchar(64) DEFAULT '' COMMENT '最后修改人邮箱前缀', + `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + PRIMARY KEY (`Id`), + KEY `IX_SpanId` (`SpanId`), + KEY `IX_DataChange_CreatedTime` (`DataChange_CreatedTime`), + KEY `IX_EntityId` (`InfluenceEntityId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志数据变动表'; \ No newline at end of file diff --git a/scripts/sql/delta/v210-v220/apolloportaldb-v210-v220.sql b/scripts/sql/delta/v210-v220/apolloportaldb-v210-v220.sql index 086769c357e..ebc206a9807 100644 --- a/scripts/sql/delta/v210-v220/apolloportaldb-v210-v220.sql +++ b/scripts/sql/delta/v210-v220/apolloportaldb-v210-v220.sql @@ -28,4 +28,52 @@ ALTER TABLE `Favorite` ALTER TABLE `Favorite` DROP INDEX `AppId`, - ADD INDEX `AppId` (`AppId`); \ No newline at end of file + ADD INDEX `AppId` (`AppId`); + +DROP TABLE IF EXISTS `AuditLog`; + +CREATE TABLE `AuditLog` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `TraceId` varchar(32) NOT NULL DEFAULT '' COMMENT '链路全局唯一ID', + `SpanId` varchar(32) NOT NULL DEFAULT '' COMMENT '跨度ID', + `ParentSpanId` varchar(32) DEFAULT NULL COMMENT '父跨度ID', + `FollowsFromSpanId` varchar(32) DEFAULT NULL COMMENT '上一个兄弟跨度ID', + `Operator` varchar(64) NOT NULL DEFAULT 'anonymous' COMMENT '操作人', + `OpType` varchar(50) NOT NULL DEFAULT 'default' COMMENT '操作类型', + `OpName` varchar(150) NOT NULL DEFAULT 'default' COMMENT '操作名称', + `Description` varchar(200) DEFAULT NULL COMMENT '备注', + `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', + `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds', + `DataChange_CreatedBy` varchar(64) DEFAULT NULL COMMENT '创建人邮箱前缀', + `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `DataChange_LastModifiedBy` varchar(64) DEFAULT '' COMMENT '最后修改人邮箱前缀', + `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + PRIMARY KEY (`Id`), + KEY `IX_TraceId` (`TraceId`), + KEY `IX_OpName` (`OpName`), + KEY `IX_DataChange_CreatedTime` (`DataChange_CreatedTime`), + KEY `IX_Operator` (`Operator`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志表'; + + +DROP TABLE IF EXISTS `AuditLogDataInfluence`; + +CREATE TABLE `AuditLogDataInfluence` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `SpanId` char(32) NOT NULL DEFAULT '' COMMENT '跨度ID', + `InfluenceEntityId` varchar(50) NOT NULL DEFAULT '0' COMMENT '记录ID', + `InfluenceEntityName` varchar(50) NOT NULL DEFAULT 'default' COMMENT '表名', + `FieldName` varchar(50) DEFAULT NULL COMMENT '字段名称', + `FieldOldValue` varchar(500) DEFAULT NULL COMMENT '字段旧值', + `FieldNewValue` varchar(500) DEFAULT NULL COMMENT '字段新值', + `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', + `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds', + `DataChange_CreatedBy` varchar(64) DEFAULT NULL COMMENT '创建人邮箱前缀', + `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `DataChange_LastModifiedBy` varchar(64) DEFAULT '' COMMENT '最后修改人邮箱前缀', + `DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + PRIMARY KEY (`Id`), + KEY `IX_SpanId` (`SpanId`), + KEY `IX_DataChange_CreatedTime` (`DataChange_CreatedTime`), + KEY `IX_EntityId` (`InfluenceEntityId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志数据变动表'; \ No newline at end of file