Commit 9f1740e0 authored by Jason Song's avatar Jason Song Committed by GitHub

Merge pull request #472 from lepdou/email

support send email when namespace released
parents eadb0a59 72923ce6
...@@ -15,6 +15,7 @@ import org.springframework.data.domain.Pageable; ...@@ -15,6 +15,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Type; import java.lang.reflect.Type;
...@@ -39,25 +40,58 @@ public class ReleaseHistoryController { ...@@ -39,25 +40,58 @@ public class ReleaseHistoryController {
method = RequestMethod.GET) method = RequestMethod.GET)
public PageDTO<ReleaseHistoryDTO> findReleaseHistoriesByNamespace( public PageDTO<ReleaseHistoryDTO> findReleaseHistoriesByNamespace(
@PathVariable String appId, @PathVariable String clusterName, @PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, Pageable pageable) { @PathVariable String namespaceName,
Page<ReleaseHistory> result = releaseHistoryService.findReleaseHistoriesByNamespace(appId, Pageable pageable) {
clusterName, namespaceName, pageable);
if (!result.hasContent()) { Page<ReleaseHistory> result = releaseHistoryService.findReleaseHistoriesByNamespace(appId, clusterName,
namespaceName, pageable);
return transform2PageDTO(result, pageable);
}
@RequestMapping(value = "/releases/histories/by_release_id_and_operation", method = RequestMethod.GET)
public PageDTO<ReleaseHistoryDTO> findReleaseHistoryByReleaseIdAndOperation(
@RequestParam("releaseId") long releaseId,
@RequestParam("operation") int operation,
Pageable pageable) {
Page<ReleaseHistory> result = releaseHistoryService.findByReleaseIdAndOperation(releaseId, operation, pageable);
return transform2PageDTO(result, pageable);
}
@RequestMapping(value = "/releases/histories/by_previous_release_id_and_operation", method = RequestMethod.GET)
public PageDTO<ReleaseHistoryDTO> findReleaseHistoryByPreviousReleaseIdAndOperation(
@RequestParam("previousReleaseId") long previousReleaseId,
@RequestParam("operation") int operation,
Pageable pageable) {
Page<ReleaseHistory> result = releaseHistoryService.findByPreviousReleaseIdAndOperation(previousReleaseId, operation, pageable);
return transform2PageDTO(result, pageable);
}
private PageDTO<ReleaseHistoryDTO> transform2PageDTO(Page<ReleaseHistory> releaseHistoriesPage, Pageable pageable){
if (!releaseHistoriesPage.hasContent()) {
return null; return null;
} }
List<ReleaseHistory> releaseHistories = result.getContent(); List<ReleaseHistory> releaseHistories = releaseHistoriesPage.getContent();
List<ReleaseHistoryDTO> releaseHistoryDTOs = new ArrayList<>(releaseHistories.size()); List<ReleaseHistoryDTO> releaseHistoryDTOs = new ArrayList<>(releaseHistories.size());
for (ReleaseHistory releaseHistory : releaseHistories) { for (ReleaseHistory releaseHistory : releaseHistories) {
ReleaseHistoryDTO dto = new ReleaseHistoryDTO(); releaseHistoryDTOs.add(transformReleaseHistory2DTO(releaseHistory));
BeanUtils.copyProperties(releaseHistory, dto, "operationContext");
dto.setOperationContext(gson.fromJson(releaseHistory.getOperationContext(),
configurationTypeReference));
releaseHistoryDTOs.add(dto);
} }
return new PageDTO<>(releaseHistoryDTOs, pageable, result.getTotalElements()); return new PageDTO<>(releaseHistoryDTOs, pageable, releaseHistoriesPage.getTotalElements());
}
private ReleaseHistoryDTO transformReleaseHistory2DTO(ReleaseHistory releaseHistory) {
ReleaseHistoryDTO dto = new ReleaseHistoryDTO();
BeanUtils.copyProperties(releaseHistory, dto, "operationContext");
dto.setOperationContext(gson.fromJson(releaseHistory.getOperationContext(),
configurationTypeReference));
return dto;
} }
} }
...@@ -24,6 +24,16 @@ public class Namespace extends BaseEntity { ...@@ -24,6 +24,16 @@ public class Namespace extends BaseEntity {
@Column(name = "NamespaceName", nullable = false) @Column(name = "NamespaceName", nullable = false)
private String namespaceName; private String namespaceName;
public Namespace(){
}
public Namespace(String appId, String clusterName, String namespaceName) {
this.appId = appId;
this.clusterName = clusterName;
this.namespaceName = namespaceName;
}
public String getAppId() { public String getAppId() {
return appId; return appId;
} }
......
...@@ -17,7 +17,9 @@ public interface ReleaseHistoryRepository extends PagingAndSortingRepository<Rel ...@@ -17,7 +17,9 @@ public interface ReleaseHistoryRepository extends PagingAndSortingRepository<Rel
Page<ReleaseHistory> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String Page<ReleaseHistory> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String
clusterName, String namespaceName, Pageable pageable); clusterName, String namespaceName, Pageable pageable);
List<ReleaseHistory> findByReleaseId(long releaseId); Page<ReleaseHistory> findByReleaseIdAndOperationOrderByIdDesc(long releaseId, int operation, Pageable pageable);
Page<ReleaseHistory> findByPreviousReleaseIdAndOperationOrderByIdDesc(long previousReleaseId, int operation, Pageable pageable);
@Modifying @Modifying
@Query("update ReleaseHistory set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3") @Query("update ReleaseHistory set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
......
...@@ -100,6 +100,10 @@ public class NamespaceService { ...@@ -100,6 +100,10 @@ public class NamespaceService {
} }
public Namespace findParentNamespace(String appId, String clusterName, String namespaceName){
return findParentNamespace(new Namespace(appId, clusterName, namespaceName));
}
public Namespace findParentNamespace(Namespace namespace) { public Namespace findParentNamespace(Namespace namespace) {
String appId = namespace.getAppId(); String appId = namespace.getAppId();
String namespaceName = namespace.getNamespaceName(); String namespaceName = namespace.getNamespaceName();
...@@ -113,6 +117,10 @@ public class NamespaceService { ...@@ -113,6 +117,10 @@ public class NamespaceService {
return null; return null;
} }
public boolean isChildNamespace(String appId, String clusterName, String namespaceName){
return isChildNamespace(new Namespace(appId, clusterName, namespaceName));
}
public boolean isChildNamespace(Namespace namespace) { public boolean isChildNamespace(Namespace namespace) {
return findParentNamespace(namespace) != null; return findParentNamespace(namespace) != null;
} }
......
...@@ -13,7 +13,6 @@ import org.springframework.stereotype.Service; ...@@ -13,7 +13,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
...@@ -21,13 +20,13 @@ import java.util.Map; ...@@ -21,13 +20,13 @@ import java.util.Map;
*/ */
@Service @Service
public class ReleaseHistoryService { public class ReleaseHistoryService {
private Gson gson = new Gson();
@Autowired @Autowired
private ReleaseHistoryRepository releaseHistoryRepository; private ReleaseHistoryRepository releaseHistoryRepository;
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
private Gson gson = new Gson();
public Page<ReleaseHistory> findReleaseHistoriesByNamespace(String appId, String clusterName, public Page<ReleaseHistory> findReleaseHistoriesByNamespace(String appId, String clusterName,
String namespaceName, Pageable String namespaceName, Pageable
...@@ -36,14 +35,18 @@ public class ReleaseHistoryService { ...@@ -36,14 +35,18 @@ public class ReleaseHistoryService {
namespaceName, pageable); namespaceName, pageable);
} }
public List<ReleaseHistory> findReleaseHistoriesByReleaseId(long releaseId) { public Page<ReleaseHistory> findByReleaseIdAndOperation(long releaseId, int operation, Pageable page) {
return releaseHistoryRepository.findByReleaseId(releaseId); return releaseHistoryRepository.findByReleaseIdAndOperationOrderByIdDesc(releaseId, operation, page);
}
public Page<ReleaseHistory> findByPreviousReleaseIdAndOperation(long previousReleaseId, int operation, Pageable page) {
return releaseHistoryRepository.findByPreviousReleaseIdAndOperationOrderByIdDesc(previousReleaseId, operation, page);
} }
@Transactional @Transactional
public ReleaseHistory createReleaseHistory(String appId, String clusterName, String public ReleaseHistory createReleaseHistory(String appId, String clusterName, String
namespaceName, String branchName, long releaseId, long previousReleaseId, int operation, namespaceName, String branchName, long releaseId, long previousReleaseId, int operation,
Map<String, Object> operationContext, String operator) { Map<String, Object> operationContext, String operator) {
ReleaseHistory releaseHistory = new ReleaseHistory(); ReleaseHistory releaseHistory = new ReleaseHistory();
releaseHistory.setAppId(appId); releaseHistory.setAppId(appId);
releaseHistory.setClusterName(clusterName); releaseHistory.setClusterName(clusterName);
...@@ -64,13 +67,13 @@ public class ReleaseHistoryService { ...@@ -64,13 +67,13 @@ public class ReleaseHistoryService {
releaseHistoryRepository.save(releaseHistory); releaseHistoryRepository.save(releaseHistory);
auditService.audit(ReleaseHistory.class.getSimpleName(), releaseHistory.getId(), auditService.audit(ReleaseHistory.class.getSimpleName(), releaseHistory.getId(),
Audit.OP.INSERT, releaseHistory.getDataChangeCreatedBy()); Audit.OP.INSERT, releaseHistory.getDataChangeCreatedBy());
return releaseHistory; return releaseHistory;
} }
@Transactional @Transactional
public int batchDelete(String appId, String clusterName, String namespaceName, String operator){ public int batchDelete(String appId, String clusterName, String namespaceName, String operator) {
return releaseHistoryRepository.batchDelete(appId, clusterName, namespaceName, operator); return releaseHistoryRepository.batchDelete(appId, clusterName, namespaceName, operator);
} }
} }
...@@ -2,11 +2,16 @@ package com.ctrip.framework.apollo.common.constants; ...@@ -2,11 +2,16 @@ package com.ctrip.framework.apollo.common.constants;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List;
import java.util.Map; import java.util.Map;
public interface GsonType { public interface GsonType {
Type CONFIG = new TypeToken<Map<String, String>>() {}.getType(); Type CONFIG = new TypeToken<Map<String, String>>() {}.getType();
Type RULE_ITEMS = new TypeToken<List<GrayReleaseRuleItemDTO>>() {}.getType();
} }
...@@ -64,6 +64,10 @@ ...@@ -64,6 +64,10 @@
<groupId>com.ctrip.framework.apollo-sso</groupId> <groupId>com.ctrip.framework.apollo-sso</groupId>
<artifactId>apollo-sso-ctrip</artifactId> <artifactId>apollo-sso-ctrip</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.ctrip.framework.apollo-ctrip-service</groupId>
<artifactId>apollo-email-service</artifactId>
</dependency>
</dependencies> </dependencies>
</profile> </profile>
<profile> <profile>
......
...@@ -388,6 +388,17 @@ public class AdminServiceAPI { ...@@ -388,6 +388,17 @@ public class AdminServiceAPI {
type, appId, clusterName, namespaceName, page, size).getBody(); type, appId, clusterName, namespaceName, page, size).getBody();
} }
public PageDTO<ReleaseHistoryDTO> findByReleaseIdAndOperation(Env env, long releaseId, int operation, int page, int size) {
return restTemplate.get(env,
"/releases/histories/by_release_id_and_operation?releaseId={releaseId}&operation={operation}&page={page}&size={size}",
type, releaseId, operation, page, size).getBody();
}
public PageDTO<ReleaseHistoryDTO> findByPreviousReleaseIdAndOperation(Env env, long previousReleaseId, int operation, int page, int size) {
return restTemplate.get(env,
"/releases/histories/by_previous_release_id_and_operation?previousReleaseId={releaseId}&operation={operation}&page={page}&size={size}",
type, previousReleaseId, operation, page, size).getBody();
}
} }
......
package com.ctrip.framework.apollo.portal.components.emailbuilder;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.constant.RoleType;
import com.ctrip.framework.apollo.portal.entity.bo.Email;
import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.entity.vo.Change;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult;
import com.ctrip.framework.apollo.portal.service.AppNamespaceService;
import com.ctrip.framework.apollo.portal.service.ReleaseService;
import com.ctrip.framework.apollo.portal.service.RolePermissionService;
import com.ctrip.framework.apollo.portal.service.ServerConfigService;
import com.ctrip.framework.apollo.portal.util.RoleUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
public abstract class ConfigPublishEmailBuilder {
//email content common field placeholder
private static final String EMAIL_CONTENT_FIELD_APPID = "\\$\\{appId\\}";
private static final String EMAIL_CONTENT_FIELD_ENV = "\\$\\{env}";
private static final String EMAIL_CONTENT_FIELD_CLUSTER = "\\$\\{clusterName}";
private static final String EMAIL_CONTENT_FIELD_NAMESPACE = "\\$\\{namespaceName}";
private static final String EMAIL_CONTENT_FIELD_OPERATOR = "\\$\\{operator}";
private static final String EMAIL_CONTENT_FIELD_RELEASE_TIME = "\\$\\{releaseTime}";
private static final String EMAIL_CONTENT_FIELD_RELEASE_ID = "\\$\\{releaseId}";
private static final String EMAIL_CONTENT_FIELD_RELEASE_TITLE = "\\$\\{releaseTitle}";
private static final String EMAIL_CONTENT_FIELD_RELEASE_COMMENT = "\\$\\{releaseComment}";
private static final String EMAIL_CONTENT_FIELD_APOLLO_SERVER_ADDRESS = "\\$\\{apollo.portal.address}";
private static final String EMAIL_CONTENT_FIELD_DIFF = "\\$\\{diff}";
//email content special field placeholder
protected static final String EMAIL_CONTENT_FIELD_RULE = "\\$\\{rules}";
//email content module switch
private static final String EMAIL_CONTENT_DIFF_HAS_CONTENT_SWITCH = "diff-hidden";
private static final String EMAIL_CONTENT_DIFF_HAS_NOT_CONTENT_SWITCH = "diff-empty-hidden";
protected static final String EMAIL_CONTENT_RULE_SWITCH = "rule-hidden";
//set config's value max length to protect email.
protected static final int VALUE_MAX_LENGTH = 100;
//email body template. config in db, so we can dynamic reject email content.
private static final String EMAIL_TEMPLATE__RELEASE = "email.template.release";
private static final String EMAIL_TEMPLATE__ROLLBACK = "email.template.rollback";
private FastDateFormat dateFormat = FastDateFormat.getInstance("yyyy-MM-dd hh:mm:ss");
private String emailAddressSuffix;
private String emailSender;
@Autowired
private ServerConfigService serverConfigService;
@Autowired
private RolePermissionService rolePermissionService;
@Autowired
private ReleaseService releaseService;
@Autowired
private AppNamespaceService appNamespaceService;
@PostConstruct
public void init() {
emailAddressSuffix = serverConfigService.getValue("email.address.suffix");
emailSender = serverConfigService.getValue("email.sender");
}
public Email build(Env env, ReleaseHistoryBO releaseHistory) {
Email email = new Email();
email.setSubject(subject());
email.setSenderEmailAddress(emailSender);
email.setBody(emailContent(env, releaseHistory));
email.setRecipients(recipients(releaseHistory.getAppId(), releaseHistory.getNamespaceName()));
return email;
}
protected abstract String subject();
protected abstract String emailContent(Env env, ReleaseHistoryBO releaseHistory);
private List<String> recipients(String appId, String namespaceName) {
Set<UserInfo> modifyRoleUsers =
rolePermissionService
.queryUsersWithRole(RoleUtils.buildNamespaceRoleName(appId, namespaceName, RoleType.MODIFY_NAMESPACE));
Set<UserInfo> releaseRoleUsers =
rolePermissionService
.queryUsersWithRole(RoleUtils.buildNamespaceRoleName(appId, namespaceName, RoleType.RELEASE_NAMESPACE));
List<String> recipients = new ArrayList<>(modifyRoleUsers.size() + releaseRoleUsers.size());
for (UserInfo userInfo : modifyRoleUsers) {
recipients.add(userInfo.getUserId() + emailAddressSuffix);
}
for (UserInfo userInfo : releaseRoleUsers) {
recipients.add(userInfo.getUserId() + emailAddressSuffix);
}
return recipients;
}
protected String renderEmailCommonContent(String template, Env env, ReleaseHistoryBO releaseHistory) {
String renderResult = renderReleaseBasicInfo(template, env, releaseHistory);
renderResult = renderDiffContent(renderResult, env, releaseHistory);
return renderResult;
}
private String renderReleaseBasicInfo(String template, Env env, ReleaseHistoryBO releaseHistory) {
String renderResult = template.replaceAll(EMAIL_CONTENT_FIELD_APPID, releaseHistory.getAppId());
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_ENV, env.toString());
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_CLUSTER, releaseHistory.getClusterName());
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_NAMESPACE, releaseHistory.getNamespaceName());
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_OPERATOR, releaseHistory.getOperator());
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_RELEASE_TITLE, releaseHistory.getReleaseTitle());
renderResult =
renderResult.replaceAll(EMAIL_CONTENT_FIELD_RELEASE_ID, String.valueOf(releaseHistory.getReleaseId()));
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_RELEASE_COMMENT, releaseHistory.getReleaseComment());
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_APOLLO_SERVER_ADDRESS, getApolloPortalAddress());
return renderResult
.replaceAll(EMAIL_CONTENT_FIELD_RELEASE_TIME, dateFormat.format(releaseHistory.getReleaseTime()));
}
private String renderDiffContent(String template, Env env, ReleaseHistoryBO releaseHistory) {
String appId = releaseHistory.getAppId();
String namespaceName = releaseHistory.getNamespaceName();
AppNamespace appNamespace = appNamespaceService.findByAppIdAndName(appId, namespaceName);
if (appNamespace == null) {
appNamespace = appNamespaceService.findPublicAppNamespace(namespaceName);
}
//don't show diff content if namespace's format is file
if (appNamespace == null ||
!appNamespace.getFormat().equals(ConfigFileFormat.Properties.getValue())) {
return template;
}
ReleaseCompareResult result = getReleaseCompareResult(env, releaseHistory);
if (!result.hasContent()) {
String renderResult = template.replaceAll(EMAIL_CONTENT_DIFF_HAS_NOT_CONTENT_SWITCH, "");
return renderResult.replaceAll(EMAIL_CONTENT_FIELD_DIFF, "");
}
List<Change> changes = result.getChanges();
StringBuilder changesHtmlBuilder = new StringBuilder();
for (Change change : changes) {
String key = change.getEntity().getFirstEntity().getKey();
String oldValue = change.getEntity().getFirstEntity().getValue();
String newValue = change.getEntity().getSecondEntity().getValue();
newValue = newValue == null ? "" : newValue;
changesHtmlBuilder.append("<tr>");
changesHtmlBuilder.append("<td width=\"10%\">").append(change.getType().toString()).append("</td>");
changesHtmlBuilder.append("<td width=\"10%\">").append(cutOffString(key)).append("</td>");
changesHtmlBuilder.append("<td width=\"10%\">").append(cutOffString(oldValue)).append("</td>");
changesHtmlBuilder.append("<td width=\"10%\">").append(cutOffString(newValue)).append("</td>");
changesHtmlBuilder.append("</tr>");
}
String renderResult = template.replaceAll(EMAIL_CONTENT_FIELD_DIFF, changesHtmlBuilder.toString());
return renderResult.replaceAll(EMAIL_CONTENT_DIFF_HAS_CONTENT_SWITCH, "");
}
private ReleaseCompareResult getReleaseCompareResult(Env env, ReleaseHistoryBO releaseHistory) {
if (releaseHistory.getOperation() == ReleaseOperation.GRAY_RELEASE
&& releaseHistory.getPreviousReleaseId() == 0) {
ReleaseDTO masterLatestActiveRelease = releaseService.loadLatestRelease(
releaseHistory.getAppId(), env, releaseHistory.getClusterName(), releaseHistory.getNamespaceName());
ReleaseDTO branchLatestActiveRelease = releaseService.findReleaseById(env, releaseHistory.getReleaseId());
return releaseService.compare(masterLatestActiveRelease, branchLatestActiveRelease);
}
return releaseService.compare(env, releaseHistory.getPreviousReleaseId(), releaseHistory.getReleaseId());
}
protected String getReleaseTemplate() {
return serverConfigService.getValue(EMAIL_TEMPLATE__RELEASE);
}
protected String getRollbackTemplate() {
return serverConfigService.getValue(EMAIL_TEMPLATE__ROLLBACK);
}
protected String getApolloPortalAddress() {
return serverConfigService.getValue("apollo.portal.address");
}
private String cutOffString(String source) {
if (source.length() > VALUE_MAX_LENGTH) {
return source.substring(0, VALUE_MAX_LENGTH) + "...";
}
return source;
}
}
package com.ctrip.framework.apollo.portal.components.emailbuilder;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Component
public class GrayPublishEmailBuilder extends ConfigPublishEmailBuilder {
private static final String EMAIL_SUBJECT = "[Apollo] 灰度发布";
private Gson gson = new Gson();
@Override
protected String subject() {
return EMAIL_SUBJECT;
}
@Override
public String emailContent(Env env, ReleaseHistoryBO releaseHistory) {
String result = renderEmailCommonContent(getReleaseTemplate(), env, releaseHistory);
return renderGrayReleaseRuleContent(result, releaseHistory);
}
private String renderGrayReleaseRuleContent(String template, ReleaseHistoryBO releaseHistory) {
String result = template.replaceAll(EMAIL_CONTENT_RULE_SWITCH, "");
Map<String, Object> context = releaseHistory.getOperationContext();
Object rules = context.get("rules");
List<GrayReleaseRuleItemDTO>
ruleItems = rules == null ?
null : gson.fromJson(rules.toString(), GsonType.RULE_ITEMS);
StringBuilder rulesHtmlBuilder = new StringBuilder();
if (CollectionUtils.isEmpty(ruleItems)) {
rulesHtmlBuilder.append("无灰度规则");
} else {
for (GrayReleaseRuleItemDTO ruleItem : ruleItems) {
String clientAppId = ruleItem.getClientAppId();
Set<String> ips = ruleItem.getClientIpList();
rulesHtmlBuilder.append("<b>AppId:&nbsp;</b>")
.append(clientAppId)
.append("&nbsp;&nbsp; <b>IP:&nbsp;</b>");
for (String ip : ips) {
rulesHtmlBuilder.append(ip).append(",");
}
}
}
return result.replaceAll(EMAIL_CONTENT_FIELD_RULE, rulesHtmlBuilder.toString());
}
}
package com.ctrip.framework.apollo.portal.components.emailbuilder;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
import org.springframework.stereotype.Component;
@Component
public class MergeEmailBuilder extends ConfigPublishEmailBuilder {
private static final String EMAIL_SUBJECT = "[Apollo] 全量发布";
@Override
protected String subject() {
return EMAIL_SUBJECT;
}
@Override
protected String emailContent(Env env, ReleaseHistoryBO releaseHistory) {
String template = getReleaseTemplate();
return renderEmailCommonContent(template, env, releaseHistory);
}
}
package com.ctrip.framework.apollo.portal.components.emailbuilder;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
import org.springframework.stereotype.Component;
@Component
public class NormalPublishEmailBuilder extends ConfigPublishEmailBuilder {
private static final String EMAIL_SUBJECT = "[Apollo] 配置发布";
@Override
protected String subject() {
return EMAIL_SUBJECT;
}
@Override
protected String emailContent(Env env, ReleaseHistoryBO releaseHistory) {
String template = getReleaseTemplate();
return renderEmailCommonContent(template, env, releaseHistory);
}
}
package com.ctrip.framework.apollo.portal.components.emailbuilder;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
import org.springframework.stereotype.Component;
@Component
public class RollbackEmailBuilder extends ConfigPublishEmailBuilder {
private static final String EMAIL_SUBJECT = "[Apollo] 配置回滚";
@Override
protected String subject() {
return EMAIL_SUBJECT;
}
@Override
protected String emailContent(Env env, ReleaseHistoryBO releaseHistory) {
String template = getRollbackTemplate();
return renderEmailCommonContent(template, env, releaseHistory);
}
}
...@@ -11,7 +11,7 @@ import com.ctrip.framework.apollo.common.utils.InputValidator; ...@@ -11,7 +11,7 @@ import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.common.utils.RequestPrecondition; import com.ctrip.framework.apollo.common.utils.RequestPrecondition;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.components.PortalSettings; import com.ctrip.framework.apollo.portal.components.PortalSettings;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.entity.vo.EnvClusterInfo; import com.ctrip.framework.apollo.portal.entity.vo.EnvClusterInfo;
import com.ctrip.framework.apollo.portal.listener.AppCreationEvent; import com.ctrip.framework.apollo.portal.listener.AppCreationEvent;
import com.ctrip.framework.apollo.portal.service.AppService; import com.ctrip.framework.apollo.portal.service.AppService;
......
...@@ -7,10 +7,12 @@ import com.ctrip.framework.apollo.core.enums.Env; ...@@ -7,10 +7,12 @@ import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.components.PermissionValidator; import com.ctrip.framework.apollo.portal.components.PermissionValidator;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO;
import com.ctrip.framework.apollo.portal.listener.ConfigPublishEvent;
import com.ctrip.framework.apollo.portal.service.NamespaceBranchService; import com.ctrip.framework.apollo.portal.service.NamespaceBranchService;
import com.ctrip.framework.apollo.portal.service.ReleaseService; import com.ctrip.framework.apollo.portal.service.ReleaseService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
...@@ -29,6 +31,8 @@ public class NamespaceBranchController { ...@@ -29,6 +31,8 @@ public class NamespaceBranchController {
private ReleaseService releaseService; private ReleaseService releaseService;
@Autowired @Autowired
private NamespaceBranchService namespaceBranchService; private NamespaceBranchService namespaceBranchService;
@Autowired
private ApplicationEventPublisher publisher;
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches") @RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches")
public NamespaceVO findBranch(@PathVariable String appId, public NamespaceVO findBranch(@PathVariable String appId,
...@@ -80,9 +84,20 @@ public class NamespaceBranchController { ...@@ -80,9 +84,20 @@ public class NamespaceBranchController {
@PathVariable String branchName, @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch, @PathVariable String branchName, @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
@RequestBody NamespaceReleaseModel model) { @RequestBody NamespaceReleaseModel model) {
return namespaceBranchService.merge(appId, Env.valueOf(env), clusterName, namespaceName, branchName, ReleaseDTO createdRelease = namespaceBranchService.merge(appId, Env.valueOf(env), clusterName, namespaceName, branchName,
model.getReleaseTitle(), model.getReleaseComment(), deleteBranch); model.getReleaseTitle(), model.getReleaseComment(), deleteBranch);
ConfigPublishEvent event = ConfigPublishEvent.instance();
event.withAppId(appId)
.withCluster(clusterName)
.withNamespace(namespaceName)
.withReleaseId(createdRelease.getId())
.setMergeEvent(true)
.setEnv(Env.valueOf(env));
publisher.publishEvent(event);
return createdRelease;
} }
......
...@@ -5,7 +5,7 @@ import com.google.common.collect.Sets; ...@@ -5,7 +5,7 @@ import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.RequestPrecondition; import com.ctrip.framework.apollo.common.utils.RequestPrecondition;
import com.ctrip.framework.apollo.portal.constant.RoleType; import com.ctrip.framework.apollo.portal.constant.RoleType;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.entity.vo.AppRolesAssignedUsers; import com.ctrip.framework.apollo.portal.entity.vo.AppRolesAssignedUsers;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceRolesAssignedUsers; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceRolesAssignedUsers;
import com.ctrip.framework.apollo.portal.entity.vo.PermissionCondition; import com.ctrip.framework.apollo.portal.entity.vo.PermissionCondition;
......
...@@ -6,9 +6,11 @@ import com.ctrip.framework.apollo.core.enums.Env; ...@@ -6,9 +6,11 @@ import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult; import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseVO; import com.ctrip.framework.apollo.portal.entity.vo.ReleaseVO;
import com.ctrip.framework.apollo.portal.listener.ConfigPublishEvent;
import com.ctrip.framework.apollo.portal.service.ReleaseService; import com.ctrip.framework.apollo.portal.service.ReleaseService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
...@@ -26,9 +28,11 @@ public class ReleaseController { ...@@ -26,9 +28,11 @@ public class ReleaseController {
@Autowired @Autowired
private ReleaseService releaseService; private ReleaseService releaseService;
@Autowired
private ApplicationEventPublisher publisher;
@PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName)") @PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName)")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/release", method = RequestMethod.POST) @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
public ReleaseDTO createRelease(@PathVariable String appId, public ReleaseDTO createRelease(@PathVariable String appId,
@PathVariable String env, @PathVariable String clusterName, @PathVariable String env, @PathVariable String clusterName,
@PathVariable String namespaceName, @RequestBody NamespaceReleaseModel model) { @PathVariable String namespaceName, @RequestBody NamespaceReleaseModel model) {
...@@ -39,7 +43,48 @@ public class ReleaseController { ...@@ -39,7 +43,48 @@ public class ReleaseController {
model.setClusterName(clusterName); model.setClusterName(clusterName);
model.setNamespaceName(namespaceName); model.setNamespaceName(namespaceName);
return releaseService.publish(model); ReleaseDTO createdRelease = releaseService.publish(model);
ConfigPublishEvent event = ConfigPublishEvent.instance();
event.withAppId(appId)
.withCluster(clusterName)
.withNamespace(namespaceName)
.withReleaseId(createdRelease.getId())
.setNormalPublishEvent(true)
.setEnv(Env.valueOf(env));
publisher.publishEvent(event);
return createdRelease;
}
@PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName)")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/releases",
method = RequestMethod.POST)
public ReleaseDTO createGrayRelease(@PathVariable String appId,
@PathVariable String env, @PathVariable String clusterName,
@PathVariable String namespaceName, @PathVariable String branchName,
@RequestBody NamespaceReleaseModel model) {
checkModel(model != null);
model.setAppId(appId);
model.setEnv(env);
model.setClusterName(branchName);
model.setNamespaceName(namespaceName);
ReleaseDTO createdRelease = releaseService.publish(model);
ConfigPublishEvent event = ConfigPublishEvent.instance();
event.withAppId(appId)
.withCluster(clusterName)
.withNamespace(namespaceName)
.withReleaseId(createdRelease.getId())
.setGrayPublishEvent(true)
.setEnv(Env.valueOf(env));
publisher.publishEvent(event);
return createdRelease;
} }
...@@ -84,5 +129,19 @@ public class ReleaseController { ...@@ -84,5 +129,19 @@ public class ReleaseController {
public void rollback(@PathVariable String env, public void rollback(@PathVariable String env,
@PathVariable long releaseId) { @PathVariable long releaseId) {
releaseService.rollback(Env.valueOf(env), releaseId); releaseService.rollback(Env.valueOf(env), releaseId);
ReleaseDTO release = releaseService.findReleaseById(Env.valueOf(env), releaseId);
if (release == null) {
return;
}
ConfigPublishEvent event = ConfigPublishEvent.instance();
event.withAppId(release.getAppId())
.withCluster(release.getClusterName())
.withNamespace(release.getNamespaceName())
.withPreviousReleaseId(releaseId)
.setRollbackEvent(true)
.setEnv(Env.valueOf(env));
publisher.publishEvent(event);
} }
} }
...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.portal.controller; ...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseHistoryVO; import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
import com.ctrip.framework.apollo.portal.service.ReleaseHistoryService; import com.ctrip.framework.apollo.portal.service.ReleaseHistoryService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -22,7 +22,7 @@ public class ReleaseHistoryController { ...@@ -22,7 +22,7 @@ public class ReleaseHistoryController {
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases/histories", @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases/histories",
method = RequestMethod.GET) method = RequestMethod.GET)
public List<ReleaseHistoryVO> findReleaseHistoriesByNamespace(@PathVariable String appId, public List<ReleaseHistoryBO> findReleaseHistoriesByNamespace(@PathVariable String appId,
@PathVariable String env, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String clusterName,
@PathVariable String namespaceName, @PathVariable String namespaceName,
......
package com.ctrip.framework.apollo.portal.controller; package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
import com.ctrip.framework.apollo.portal.spi.LogoutHandler; import com.ctrip.framework.apollo.portal.spi.LogoutHandler;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserService; import com.ctrip.framework.apollo.portal.spi.UserService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
......
package com.ctrip.framework.apollo.portal.entity.bo;
import java.util.List;
public class Email {
private String senderEmailAddress;
private List<String> recipients;
private String subject;
private String body;
public String getSenderEmailAddress() {
return senderEmailAddress;
}
public void setSenderEmailAddress(String senderEmailAddress) {
this.senderEmailAddress = senderEmailAddress;
}
public List<String> getRecipients() {
return recipients;
}
public void setRecipients(List<String> recipients) {
this.recipients = recipients;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
package com.ctrip.framework.apollo.portal.entity.vo; package com.ctrip.framework.apollo.portal.entity.bo;
import com.ctrip.framework.apollo.common.entity.EntityPair; import com.ctrip.framework.apollo.common.entity.EntityPair;
...@@ -6,7 +6,7 @@ import java.util.Date; ...@@ -6,7 +6,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class ReleaseHistoryVO { public class ReleaseHistoryBO {
private long id; private long id;
......
package com.ctrip.framework.apollo.portal.entity.po; package com.ctrip.framework.apollo.portal.entity.bo;
public class UserInfo { public class UserInfo {
......
package com.ctrip.framework.apollo.portal.entity.vo; package com.ctrip.framework.apollo.portal.entity.vo;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import java.util.Set; import java.util.Set;
......
package com.ctrip.framework.apollo.portal.entity.vo; package com.ctrip.framework.apollo.portal.entity.vo;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import java.util.Set; import java.util.Set;
......
...@@ -14,6 +14,10 @@ public class ReleaseCompareResult { ...@@ -14,6 +14,10 @@ public class ReleaseCompareResult {
changes.add(new Change(type, new EntityPair<>(firstEntity, secondEntity))); changes.add(new Change(type, new EntityPair<>(firstEntity, secondEntity)));
} }
public boolean hasContent(){
return !changes.isEmpty();
}
public List<Change> getChanges() { public List<Change> getChanges() {
return changes; return changes;
} }
......
package com.ctrip.framework.apollo.portal.listener;
import com.ctrip.framework.apollo.core.enums.Env;
import org.springframework.context.ApplicationEvent;
public class ConfigPublishEvent extends ApplicationEvent {
private ConfigPublishInfo configPublishInfo;
public ConfigPublishEvent(Object source) {
super(source);
configPublishInfo = (ConfigPublishInfo) source;
}
public static ConfigPublishEvent instance() {
ConfigPublishInfo info = new ConfigPublishInfo();
return new ConfigPublishEvent(info);
}
public ConfigPublishInfo getConfigPublishInfo(){
return configPublishInfo;
}
public ConfigPublishEvent withAppId(String appId) {
configPublishInfo.setAppId(appId);
return this;
}
public ConfigPublishEvent withCluster(String clusterName) {
configPublishInfo.setClusterName(clusterName);
return this;
}
public ConfigPublishEvent withNamespace(String namespaceName) {
configPublishInfo.setNamespaceName(namespaceName);
return this;
}
public ConfigPublishEvent withReleaseId(long releaseId){
configPublishInfo.setReleaseId(releaseId);
return this;
}
public ConfigPublishEvent withPreviousReleaseId(long previousReleaseId){
configPublishInfo.setPreviousReleaseId(previousReleaseId);
return this;
}
public ConfigPublishEvent setNormalPublishEvent(boolean isNormalPublishEvent) {
configPublishInfo.setNormalPublishEvent(isNormalPublishEvent);
return this;
}
public ConfigPublishEvent setGrayPublishEvent(boolean isGrayPublishEvent) {
configPublishInfo.setGrayPublishEvent(isGrayPublishEvent);
return this;
}
public ConfigPublishEvent setRollbackEvent(boolean isRollbackEvent) {
configPublishInfo.setRollbackEvent(isRollbackEvent);
return this;
}
public ConfigPublishEvent setMergeEvent(boolean isMergeEvent) {
configPublishInfo.setMergeEvent(isMergeEvent);
return this;
}
public ConfigPublishEvent setEnv(Env env) {
configPublishInfo.setEnv(env);
return this;
}
public static class ConfigPublishInfo {
private Env env;
private String appId;
private String clusterName;
private String namespaceName;
private long releaseId;
private long previousReleaseId;
private boolean isRollbackEvent;
private boolean isMergeEvent;
private boolean isNormalPublishEvent;
private boolean isGrayPublishEvent;
public Env getEnv() {
return env;
}
public void setEnv(Env env) {
this.env = env;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
public long getReleaseId() {
return releaseId;
}
public void setReleaseId(long releaseId) {
this.releaseId = releaseId;
}
public long getPreviousReleaseId() {
return previousReleaseId;
}
public void setPreviousReleaseId(long previousReleaseId) {
this.previousReleaseId = previousReleaseId;
}
public boolean isRollbackEvent() {
return isRollbackEvent;
}
public void setRollbackEvent(boolean rollbackEvent) {
isRollbackEvent = rollbackEvent;
}
public boolean isMergeEvent() {
return isMergeEvent;
}
public void setMergeEvent(boolean mergeEvent) {
isMergeEvent = mergeEvent;
}
public boolean isNormalPublishEvent() {
return isNormalPublishEvent;
}
public void setNormalPublishEvent(boolean normalPublishEvent) {
isNormalPublishEvent = normalPublishEvent;
}
public boolean isGrayPublishEvent() {
return isGrayPublishEvent;
}
public void setGrayPublishEvent(boolean grayPublishEvent) {
isGrayPublishEvent = grayPublishEvent;
}
}
}
package com.ctrip.framework.apollo.portal.listener;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.components.emailbuilder.GrayPublishEmailBuilder;
import com.ctrip.framework.apollo.portal.components.emailbuilder.MergeEmailBuilder;
import com.ctrip.framework.apollo.portal.components.emailbuilder.NormalPublishEmailBuilder;
import com.ctrip.framework.apollo.portal.components.emailbuilder.RollbackEmailBuilder;
import com.ctrip.framework.apollo.portal.entity.bo.Email;
import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
import com.ctrip.framework.apollo.portal.service.ReleaseHistoryService;
import com.ctrip.framework.apollo.portal.service.ServerConfigService;
import com.ctrip.framework.apollo.portal.spi.EmailService;
import com.ctrip.framework.apollo.tracer.Tracer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.PostConstruct;
@Component
public class ConfigPublishListener {
@Autowired
private ServerConfigService serverConfigService;
@Autowired
private ReleaseHistoryService releaseHistoryService;
@Autowired
private EmailService emailService;
@Autowired
private NormalPublishEmailBuilder normalPublishEmailBuilder;
@Autowired
private GrayPublishEmailBuilder grayPublishEmailBuilder;
@Autowired
private RollbackEmailBuilder rollbackEmailBuilder;
@Autowired
private MergeEmailBuilder mergeEmailBuilder;
private Set<Env> emailSupportedEnvs = new HashSet<>();
@PostConstruct
public void init() {
try {
String sendEmailSwitchConfig =
serverConfigService.getValue("email.supported.envs", "PRO");
String[] supportedEnvs = sendEmailSwitchConfig.split(",");
for (String env : supportedEnvs) {
emailSupportedEnvs.add(Env.fromString(env.trim()));
}
} catch (Exception e) {
Tracer.logError("init email supported envs failed.", e);
}
}
@EventListener
public void onConfigPublish(ConfigPublishEvent event) {
Env env = event.getConfigPublishInfo().getEnv();
if (!emailSupportedEnvs.contains(env)) {
return;
}
ReleaseHistoryBO releaseHistory = getReleaseHistory(event);
if (releaseHistory == null) {
Tracer.logError("Will not send email, because load release history error", null);
return;
}
int realOperation = releaseHistory.getOperation();
Email email = buildEmail(env, releaseHistory, realOperation);
if (email != null) {
emailService.send(email);
}
}
private ReleaseHistoryBO getReleaseHistory(ConfigPublishEvent event) {
ConfigPublishEvent.ConfigPublishInfo info = event.getConfigPublishInfo();
Env env = info.getEnv();
int operation = info.isMergeEvent() ? ReleaseOperation.GRAY_RELEASE_MERGE_TO_MASTER :
info.isRollbackEvent() ? ReleaseOperation.ROLLBACK :
info.isNormalPublishEvent() ? ReleaseOperation.NORMAL_RELEASE :
info.isGrayPublishEvent() ? ReleaseOperation.GRAY_RELEASE : -1;
if (operation == -1) {
return null;
}
if (info.isRollbackEvent()) {
return releaseHistoryService
.findLatestByPreviousReleaseIdAndOperation(env, info.getPreviousReleaseId(), operation);
}else {
return releaseHistoryService.findLatestByReleaseIdAndOperation(env, info.getReleaseId(), operation);
}
}
private Email buildEmail(Env env, ReleaseHistoryBO releaseHistory, int operation) {
switch (operation) {
case ReleaseOperation.GRAY_RELEASE: {
return grayPublishEmailBuilder.build(env, releaseHistory);
}
case ReleaseOperation.NORMAL_RELEASE: {
return normalPublishEmailBuilder.build(env, releaseHistory);
}
case ReleaseOperation.ROLLBACK: {
return rollbackEmailBuilder.build(env, releaseHistory);
}
case ReleaseOperation.GRAY_RELEASE_MERGE_TO_MASTER: {
return mergeEmailBuilder.build(env, releaseHistory);
}
default:
return null;
}
}
}
...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.portal.service; ...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.portal.entity.po.Favorite; import com.ctrip.framework.apollo.portal.entity.po.Favorite;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.repository.FavoriteRepository; import com.ctrip.framework.apollo.portal.repository.FavoriteRepository;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.spi.UserService; import com.ctrip.framework.apollo.portal.spi.UserService;
......
...@@ -10,7 +10,7 @@ import com.ctrip.framework.apollo.common.entity.EntityPair; ...@@ -10,7 +10,7 @@ import com.ctrip.framework.apollo.common.entity.EntityPair;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseHistoryVO; import com.ctrip.framework.apollo.portal.entity.bo.ReleaseHistoryBO;
import com.ctrip.framework.apollo.portal.util.RelativeDateFormat; import com.ctrip.framework.apollo.portal.util.RelativeDateFormat;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -36,7 +36,29 @@ public class ReleaseHistoryService { ...@@ -36,7 +36,29 @@ public class ReleaseHistoryService {
private ReleaseService releaseService; private ReleaseService releaseService;
public List<ReleaseHistoryVO> findNamespaceReleaseHistory(String appId, Env env, String clusterName, public ReleaseHistoryBO findLatestByReleaseIdAndOperation(Env env, long releaseId, int operation){
PageDTO<ReleaseHistoryDTO> pageDTO = releaseHistoryAPI.findByReleaseIdAndOperation(env, releaseId, operation, 0, 1);
if (pageDTO != null && pageDTO.hasContent()){
ReleaseHistoryDTO releaseHistory = pageDTO.getContent().get(0);
ReleaseDTO release = releaseService.findReleaseById(env, releaseHistory.getReleaseId());
return transformReleaseHistoryDTO2BO(releaseHistory, release);
}
return null;
}
public ReleaseHistoryBO findLatestByPreviousReleaseIdAndOperation(Env env, long previousReleaseId, int operation){
PageDTO<ReleaseHistoryDTO> pageDTO = releaseHistoryAPI.findByPreviousReleaseIdAndOperation(env, previousReleaseId, operation, 0, 1);
if (pageDTO != null && pageDTO.hasContent()){
ReleaseHistoryDTO releaseHistory = pageDTO.getContent().get(0);
ReleaseDTO release = releaseService.findReleaseById(env, releaseHistory.getReleaseId());
return transformReleaseHistoryDTO2BO(releaseHistory, release);
}
return null;
}
public List<ReleaseHistoryBO> findNamespaceReleaseHistory(String appId, Env env, String clusterName,
String namespaceName, int page, int size) { String namespaceName, int page, int size) {
PageDTO<ReleaseHistoryDTO> result = releaseHistoryAPI.findReleaseHistoriesByNamespace(appId, env, clusterName, PageDTO<ReleaseHistoryDTO> result = releaseHistoryAPI.findReleaseHistoriesByNamespace(appId, env, clusterName,
namespaceName, page, size); namespaceName, page, size);
...@@ -55,44 +77,47 @@ public class ReleaseHistoryService { ...@@ -55,44 +77,47 @@ public class ReleaseHistoryService {
List<ReleaseDTO> releases = releaseService.findReleaseByIds(env, releaseIds); List<ReleaseDTO> releases = releaseService.findReleaseByIds(env, releaseIds);
return convertReleaseHistoryDTO2VO(content, releases); return transformReleaseHistoryDTO2BO(content, releases);
} }
private List<ReleaseHistoryVO> convertReleaseHistoryDTO2VO(List<ReleaseHistoryDTO> source, private List<ReleaseHistoryBO> transformReleaseHistoryDTO2BO(List<ReleaseHistoryDTO> source,
List<ReleaseDTO> releases) { List<ReleaseDTO> releases) {
Map<Long, ReleaseDTO> releasesMap = BeanUtils.mapByKey("id", releases); Map<Long, ReleaseDTO> releasesMap = BeanUtils.mapByKey("id", releases);
List<ReleaseHistoryVO> vos = new ArrayList<>(source.size()); List<ReleaseHistoryBO> bos = new ArrayList<>(source.size());
for (ReleaseHistoryDTO dto : source) { for (ReleaseHistoryDTO dto : source) {
ReleaseHistoryVO vo = new ReleaseHistoryVO();
vo.setId(dto.getId());
vo.setAppId(dto.getAppId());
vo.setClusterName(dto.getClusterName());
vo.setNamespaceName(dto.getNamespaceName());
vo.setBranchName(dto.getBranchName());
vo.setReleaseId(dto.getReleaseId());
vo.setPreviousReleaseId(dto.getPreviousReleaseId());
vo.setOperator(dto.getDataChangeCreatedBy());
vo.setOperation(dto.getOperation());
Date releaseTime = dto.getDataChangeLastModifiedTime();
vo.setReleaseTime(releaseTime);
vo.setReleaseTimeFormatted(RelativeDateFormat.format(releaseTime));
vo.setOperationContext(dto.getOperationContext());
//set release info
ReleaseDTO release = releasesMap.get(dto.getReleaseId()); ReleaseDTO release = releasesMap.get(dto.getReleaseId());
setReleaseInfoToReleaseHistoryVO(vo, release); bos.add(transformReleaseHistoryDTO2BO(dto, release));
vos.add(vo);
} }
return vos; return bos;
} }
private void setReleaseInfoToReleaseHistoryVO(ReleaseHistoryVO vo, ReleaseDTO release) { private ReleaseHistoryBO transformReleaseHistoryDTO2BO(ReleaseHistoryDTO dto, ReleaseDTO release){
ReleaseHistoryBO bo = new ReleaseHistoryBO();
bo.setId(dto.getId());
bo.setAppId(dto.getAppId());
bo.setClusterName(dto.getClusterName());
bo.setNamespaceName(dto.getNamespaceName());
bo.setBranchName(dto.getBranchName());
bo.setReleaseId(dto.getReleaseId());
bo.setPreviousReleaseId(dto.getPreviousReleaseId());
bo.setOperator(dto.getDataChangeCreatedBy());
bo.setOperation(dto.getOperation());
Date releaseTime = dto.getDataChangeLastModifiedTime();
bo.setReleaseTime(releaseTime);
bo.setReleaseTimeFormatted(RelativeDateFormat.format(releaseTime));
bo.setOperationContext(dto.getOperationContext());
//set release info
setReleaseInfoToReleaseHistoryBO(bo, release);
return bo;
}
private void setReleaseInfoToReleaseHistoryBO(ReleaseHistoryBO bo, ReleaseDTO release) {
if (release != null) { if (release != null) {
vo.setReleaseTitle(release.getName()); bo.setReleaseTitle(release.getName());
vo.setReleaseComment(release.getComment()); bo.setReleaseComment(release.getComment());
Map<String, String> configuration = gson.fromJson(release.getConfigurations(), GsonType.CONFIG); Map<String, String> configuration = gson.fromJson(release.getConfigurations(), GsonType.CONFIG);
List<EntityPair<String>> items = new ArrayList<>(configuration.size()); List<EntityPair<String>> items = new ArrayList<>(configuration.size());
...@@ -100,11 +125,11 @@ public class ReleaseHistoryService { ...@@ -100,11 +125,11 @@ public class ReleaseHistoryService {
EntityPair<String> entityPair = new EntityPair<>(entry.getKey(), entry.getValue()); EntityPair<String> entityPair = new EntityPair<>(entry.getKey(), entry.getValue());
items.add(entityPair); items.add(entityPair);
} }
vo.setConfiguration(items); bo.setConfiguration(items);
} else { } else {
vo.setReleaseTitle("no release information"); bo.setReleaseTitle("no release information");
vo.setConfiguration(null); bo.setConfiguration(null);
} }
} }
} }
...@@ -2,8 +2,8 @@ package com.ctrip.framework.apollo.portal.service; ...@@ -2,8 +2,8 @@ package com.ctrip.framework.apollo.portal.service;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets; import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO; import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
...@@ -22,9 +22,9 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -22,9 +22,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
...@@ -33,10 +33,8 @@ import java.util.Set; ...@@ -33,10 +33,8 @@ import java.util.Set;
@Service @Service
public class ReleaseService { public class ReleaseService {
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
private static final Type configurationTypeReference =
new TypeToken<Map<String, String>>() {
}.getType();
@Autowired @Autowired
private UserInfoHolder userInfoHolder; private UserInfoHolder userInfoHolder;
...@@ -60,9 +58,11 @@ public class ReleaseService { ...@@ -60,9 +58,11 @@ public class ReleaseService {
} }
public ReleaseDTO updateAndPublish(String appId, Env env, String clusterName, String namespaceName, public ReleaseDTO updateAndPublish(String appId, Env env, String clusterName, String namespaceName,
String releaseTitle, String releaseComment, String branchName, boolean deleteBranch, ItemChangeSets changeSets){ String releaseTitle, String releaseComment, String branchName,
boolean deleteBranch, ItemChangeSets changeSets) {
return releaseAPI.updateAndPublish(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, branchName, deleteBranch, changeSets); return releaseAPI.updateAndPublish(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, branchName,
deleteBranch, changeSets);
} }
public List<ReleaseVO> findAllReleases(String appId, Env env, String clusterName, String namespaceName, int page, public List<ReleaseVO> findAllReleases(String appId, Env env, String clusterName, String namespaceName, int page,
...@@ -79,7 +79,7 @@ public class ReleaseService { ...@@ -79,7 +79,7 @@ public class ReleaseService {
release.setBaseInfo(releaseDTO); release.setBaseInfo(releaseDTO);
Set<KVEntity> kvEntities = new LinkedHashSet<>(); Set<KVEntity> kvEntities = new LinkedHashSet<>();
Map<String, String> configurations = gson.fromJson(releaseDTO.getConfigurations(), configurationTypeReference); Map<String, String> configurations = gson.fromJson(releaseDTO.getConfigurations(), GsonType.CONFIG);
Set<Map.Entry<String, String>> entries = configurations.entrySet(); Set<Map.Entry<String, String>> entries = configurations.entrySet();
for (Map.Entry<String, String> entry : entries) { for (Map.Entry<String, String> entry : entries) {
kvEntities.add(new KVEntity(entry.getKey(), entry.getValue())); kvEntities.add(new KVEntity(entry.getKey(), entry.getValue()));
...@@ -98,7 +98,19 @@ public class ReleaseService { ...@@ -98,7 +98,19 @@ public class ReleaseService {
return releaseAPI.findActiveReleases(appId, env, clusterName, namespaceName, page, size); return releaseAPI.findActiveReleases(appId, env, clusterName, namespaceName, page, size);
} }
public List<ReleaseDTO> findReleaseByIds(Env env, Set<Long> releaseIds){ public ReleaseDTO findReleaseById(Env env, long releaseId) {
Set<Long> releaseIds = new HashSet<>(1);
releaseIds.add(releaseId);
List<ReleaseDTO> releases = findReleaseByIds(env, releaseIds);
if (CollectionUtils.isEmpty(releases)) {
return null;
} else {
return releases.get(0);
}
}
public List<ReleaseDTO> findReleaseByIds(Env env, Set<Long> releaseIds) {
return releaseAPI.findReleaseByIds(env, releaseIds); return releaseAPI.findReleaseByIds(env, releaseIds);
} }
...@@ -111,19 +123,27 @@ public class ReleaseService { ...@@ -111,19 +123,27 @@ public class ReleaseService {
} }
public ReleaseCompareResult compare(Env env, long baseReleaseId, long toCompareReleaseId) { public ReleaseCompareResult compare(Env env, long baseReleaseId, long toCompareReleaseId) {
Map<String, String> baseReleaseConfiguration = new HashMap<>();
Map<String, String> toCompareReleaseConfiguration = new HashMap<>();
if (baseReleaseId != 0){ ReleaseDTO baseRelease = null;
ReleaseDTO baseRelease = releaseAPI.loadRelease(env, baseReleaseId); ReleaseDTO toCompareRelease = null;
baseReleaseConfiguration = gson.fromJson(baseRelease.getConfigurations(), configurationTypeReference); if (baseReleaseId != 0) {
baseRelease = releaseAPI.loadRelease(env, baseReleaseId);
} }
if (toCompareReleaseId != 0){ if (toCompareReleaseId != 0) {
ReleaseDTO toCompareRelease = releaseAPI.loadRelease(env, toCompareReleaseId); toCompareRelease = releaseAPI.loadRelease(env, toCompareReleaseId);
toCompareReleaseConfiguration = gson.fromJson(toCompareRelease.getConfigurations(), configurationTypeReference);
} }
return compare(baseRelease, toCompareRelease);
}
public ReleaseCompareResult compare(ReleaseDTO baseRelease, ReleaseDTO toCompareRelease) {
Map<String, String> baseReleaseConfiguration = baseRelease == null ? new HashMap<>() :
gson.fromJson(baseRelease.getConfigurations(), GsonType.CONFIG);
Map<String, String> toCompareReleaseConfiguration = toCompareRelease == null ? new HashMap<>() :
gson.fromJson(toCompareRelease.getConfigurations(),
GsonType.CONFIG);
ReleaseCompareResult compareResult = new ReleaseCompareResult(); ReleaseCompareResult compareResult = new ReleaseCompareResult();
//added and modified in firstRelease //added and modified in firstRelease
...@@ -134,10 +154,10 @@ public class ReleaseService { ...@@ -134,10 +154,10 @@ public class ReleaseService {
//added //added
if (secondValue == null) { if (secondValue == null) {
compareResult.addEntityPair(ChangeType.DELETED, new KVEntity(key, firstValue), compareResult.addEntityPair(ChangeType.DELETED, new KVEntity(key, firstValue),
new KVEntity(key, null)); new KVEntity(key, null));
} else if (!Objects.equal(firstValue, secondValue)) { } else if (!Objects.equal(firstValue, secondValue)) {
compareResult.addEntityPair(ChangeType.MODIFIED, new KVEntity(key, firstValue), compareResult.addEntityPair(ChangeType.MODIFIED, new KVEntity(key, firstValue),
new KVEntity(key, secondValue)); new KVEntity(key, secondValue));
} }
} }
......
...@@ -12,7 +12,7 @@ import com.google.common.collect.Sets; ...@@ -12,7 +12,7 @@ import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.portal.entity.po.Permission; import com.ctrip.framework.apollo.portal.entity.po.Permission;
import com.ctrip.framework.apollo.portal.entity.po.Role; import com.ctrip.framework.apollo.portal.entity.po.Role;
import com.ctrip.framework.apollo.portal.entity.po.RolePermission; import com.ctrip.framework.apollo.portal.entity.po.RolePermission;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.entity.po.UserRole; import com.ctrip.framework.apollo.portal.entity.po.UserRole;
import com.ctrip.framework.apollo.portal.repository.PermissionRepository; import com.ctrip.framework.apollo.portal.repository.PermissionRepository;
import com.ctrip.framework.apollo.portal.repository.RolePermissionRepository; import com.ctrip.framework.apollo.portal.repository.RolePermissionRepository;
......
package com.ctrip.framework.apollo.portal.spi;
import com.ctrip.framework.apollo.portal.entity.bo.Email;
public interface EmailService {
void send(Email email);
}
package com.ctrip.framework.apollo.portal.spi; package com.ctrip.framework.apollo.portal.spi;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
/** /**
* Get access to the user's information, * Get access to the user's information,
......
package com.ctrip.framework.apollo.portal.spi; package com.ctrip.framework.apollo.portal.spi;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import java.util.List; import java.util.List;
......
package com.ctrip.framework.apollo.portal.spi.configuration;
import com.ctrip.framework.apollo.portal.spi.EmailService;
import com.ctrip.framework.apollo.portal.spi.ctrip.CtripEmailService;
import com.ctrip.framework.apollo.portal.spi.ctrip.CtripEmailRequestBuilder;
import com.ctrip.framework.apollo.portal.spi.defaultimpl.DefaultEmailService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class EmailConfiguration {
@Configuration
@Profile("ctrip")
public static class CtripEmailConfiguration{
@Bean
public EmailService ctripEmailService() {
return new CtripEmailService();
}
@Bean
public CtripEmailRequestBuilder emailRequestBuilder(){
return new CtripEmailRequestBuilder();
}
}
@Bean
@ConditionalOnMissingBean(EmailService.class)
public EmailService defaultEmailService() {
return new DefaultEmailService();
}
}
package com.ctrip.framework.apollo.portal.spi.ctrip;
import com.ctrip.framework.apollo.portal.entity.bo.Email;
import com.ctrip.framework.apollo.tracer.Tracer;
import org.apache.commons.lang.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.annotation.PostConstruct;
public class CtripEmailRequestBuilder {
private static final Logger logger = LoggerFactory.getLogger(CtripEmailRequestBuilder.class);
private static Class sendEmailRequestClazz;
private static Method setBodyContent;
private static Method setRecipient;
private static Method setSendCode;
private static Method setBodyTemplateID;
private static Method setSender;
private static Method setSubject;
private static Method setIsBodyHtml;
private static Method setCharset;
private static Method setExpiredTime;
private static Method setAppID;
@Value("${ctrip.appid}")
private int appId;
//send code & template id. apply from ewatch
@Value("${ctrip.email.send.code}")
private String sendCode;
@Value("${ctrip.email.template.id}")
private int templateId;
//email retention time in email server queue.TimeUnit: hour
@Value("${ctrip.email.survival.duration}")
private int survivalDuration;
private Calendar expiredTime;
@PostConstruct
public void init() {
try {
sendEmailRequestClazz = Class.forName("com.ctrip.framework.apolloctripservice.emailservice.SendEmailRequest");
setSendCode = sendEmailRequestClazz.getMethod("setSendCode", String.class);
setBodyTemplateID = sendEmailRequestClazz.getMethod("setBodyTemplateID", Integer.class);
setSender = sendEmailRequestClazz.getMethod("setSender", String.class);
setBodyContent = sendEmailRequestClazz.getMethod("setBodyContent", String.class);
setRecipient = sendEmailRequestClazz.getMethod("setRecipient", List.class);
setSubject = sendEmailRequestClazz.getMethod("setSubject", String.class);
setIsBodyHtml = sendEmailRequestClazz.getMethod("setIsBodyHtml", Boolean.class);
setCharset = sendEmailRequestClazz.getMethod("setCharset", String.class);
setExpiredTime = sendEmailRequestClazz.getMethod("setExpiredTime", Calendar.class);
setAppID = sendEmailRequestClazz.getMethod("setAppID", Integer.class);
expiredTime = calExpiredTime();
} catch (Throwable e) {
logger.error("init email request build failed", e);
Tracer.logError("init email request build failed", e);
}
}
public Object buildEmailRequest(Email email) throws Exception {
Object emailRequest = createBasicEmailRequest();
setSender.invoke(emailRequest, email.getSenderEmailAddress());
setSubject.invoke(emailRequest, email.getSubject());
setBodyContent.invoke(emailRequest, email.getBody());
setRecipient.invoke(emailRequest, email.getRecipients());
return emailRequest;
}
private Object createBasicEmailRequest() throws Exception {
Object request = sendEmailRequestClazz.newInstance();
setSendCode.invoke(request, sendCode);
setBodyTemplateID.invoke(request, templateId);
setIsBodyHtml.invoke(request, true);
setCharset.invoke(request, "UTF-8");
setExpiredTime.invoke(request, expiredTime);
setAppID.invoke(request, appId);
return request;
}
private Calendar calExpiredTime() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(DateUtils.addHours(new Date(), survivalDuration));
return calendar;
}
}
package com.ctrip.framework.apollo.portal.spi.ctrip;
import com.ctrip.framework.apollo.portal.entity.bo.Email;
import com.ctrip.framework.apollo.portal.service.ServerConfigService;
import com.ctrip.framework.apollo.portal.spi.EmailService;
import com.ctrip.framework.apollo.tracer.Tracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.reflect.Method;
import javax.annotation.PostConstruct;
public class CtripEmailService implements EmailService {
private static final Logger logger = LoggerFactory.getLogger(CtripEmailService.class);
private Object emailServiceClient;
private Method sendEmailAsync;
@Autowired
private CtripEmailRequestBuilder emailRequestBuilder;
@Autowired
private ServerConfigService serverConfigService;
@PostConstruct
public void init() {
try {
initServiceClientConfig();
Class emailServiceClientClazz =
Class.forName("com.ctrip.framework.apolloctripservice.emailservice.EmailServiceClient");
Method getInstanceMethod = emailServiceClientClazz.getMethod("getInstance");
emailServiceClient = getInstanceMethod.invoke(null);
Class sendEmailRequestClazz =
Class.forName("com.ctrip.framework.apolloctripservice.emailservice.SendEmailRequest");
sendEmailAsync = emailServiceClientClazz.getMethod("sendEmailAsync", sendEmailRequestClazz);
} catch (Throwable e) {
logger.error("init ctrip email service failed", e);
Tracer.logError("init ctrip email service failed", e);
}
}
private void initServiceClientConfig() throws Exception {
Class serviceClientConfigClazz = Class.forName("com.ctriposs.baiji.rpc.client.ServiceClientConfig");
Object serviceClientConfig = serviceClientConfigClazz.newInstance();
Method setFxConfigServiceUrlMethod = serviceClientConfigClazz.getMethod("setFxConfigServiceUrl", String.class);
String soaServerAddress = serverConfigService.getValue("soa.server.address");
setFxConfigServiceUrlMethod.invoke(serviceClientConfig, soaServerAddress);
Class serviceClientBaseClazz = Class.forName("com.ctriposs.baiji.rpc.client.ServiceClientBase");
Method initializeMethod = serviceClientBaseClazz.getMethod("initialize", serviceClientConfigClazz);
initializeMethod.invoke(null, serviceClientConfig);
}
@Override
public void send(Email email) {
try {
Object emailRequest = emailRequestBuilder.buildEmailRequest(email);
Object sendResponse = sendEmailAsync.invoke(emailServiceClient, emailRequest);
logger.info("Email sender response:" + sendResponse);
} catch (Throwable e) {
logger.error("send email failed", e);
Tracer.logError("send email failed", e);
}
}
}
package com.ctrip.framework.apollo.portal.spi.ctrip; package com.ctrip.framework.apollo.portal.spi.ctrip;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import java.lang.reflect.Method; import java.lang.reflect.Method;
......
...@@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableMap; ...@@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.service.ServerConfigService; import com.ctrip.framework.apollo.portal.service.ServerConfigService;
import com.ctrip.framework.apollo.portal.spi.UserService; import com.ctrip.framework.apollo.portal.spi.UserService;
......
package com.ctrip.framework.apollo.portal.spi.defaultimpl;
import com.ctrip.framework.apollo.portal.entity.bo.Email;
import com.ctrip.framework.apollo.portal.spi.EmailService;
public class DefaultEmailService implements EmailService{
@Override
public void send(Email email){
//do nothing
}
}
package com.ctrip.framework.apollo.portal.spi.defaultimpl; package com.ctrip.framework.apollo.portal.spi.defaultimpl;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
/** /**
* 不是ctrip的公司默认提供一个假用户 * 不是ctrip的公司默认提供一个假用户
......
...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.portal.spi.defaultimpl; ...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.portal.spi.defaultimpl;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserService; import com.ctrip.framework.apollo.portal.spi.UserService;
import java.util.Arrays; import java.util.Arrays;
......
...@@ -19,7 +19,7 @@ public class RelativeDateFormat { ...@@ -19,7 +19,7 @@ public class RelativeDateFormat {
public static String format(Date date) { public static String format(Date date) {
if (date.after(new Date())) { if (date.after(new Date())) {
throw new IllegalArgumentException("To format date can not after now"); return "now";
} }
long delta = new Date().getTime() - date.getTime(); long delta = new Date().getTime() - date.getTime();
......
ctrip:
appid: 100003173
email:
send:
code: 37030033
template:
id: 37030033
survival:
duration: 5
...@@ -4,9 +4,6 @@ spring: ...@@ -4,9 +4,6 @@ spring:
profiles: profiles:
active: ${apollo_profile} active: ${apollo_profile}
ctrip:
appid: 100003173
server: server:
port: 8080 port: 8080
......
...@@ -41,6 +41,8 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na ...@@ -41,6 +41,8 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
function release() { function release() {
if (scope.toReleaseNamespace.mergeAndPublish) { if (scope.toReleaseNamespace.mergeAndPublish) {
mergeAndPublish(); mergeAndPublish();
} else if (scope.toReleaseNamespace.isBranch) {
grayPublish();
} else { } else {
publish(); publish();
} }
...@@ -49,7 +51,7 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na ...@@ -49,7 +51,7 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
function publish() { function publish() {
scope.releaseBtnDisabled = true; scope.releaseBtnDisabled = true;
ReleaseService.release(scope.appId, scope.env, ReleaseService.publish(scope.appId, scope.env,
scope.toReleaseNamespace.baseInfo.clusterName, scope.toReleaseNamespace.baseInfo.clusterName,
scope.toReleaseNamespace.baseInfo.namespaceName, scope.toReleaseNamespace.baseInfo.namespaceName,
scope.toReleaseNamespace.releaseTitle, scope.toReleaseNamespace.releaseTitle,
...@@ -57,38 +59,13 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na ...@@ -57,38 +59,13 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
function (result) { function (result) {
AppUtil.hideModal('#releaseModal'); AppUtil.hideModal('#releaseModal');
toastr.success("发布成功"); toastr.success("发布成功");
//refresh all namespace items
scope.releaseBtnDisabled = false;
//gray release scope.releaseBtnDisabled = false;
if (scope.toReleaseNamespace.isBranch
&& !scope.toReleaseNamespace.mergeAndPublish) {
//refresh item status
scope.toReleaseNamespace.branchItems.forEach(function (item, index) {
if (item.isDeleted) {
scope.toReleaseNamespace.branchItems.splice(index, 1);
} else {
item.isModified = false;
}
});
scope.toReleaseNamespace.itemModifiedCnt = 0;
//check rules
if (!scope.toReleaseNamespace.rules
|| !scope.toReleaseNamespace.rules.ruleItems
|| !scope.toReleaseNamespace.rules.ruleItems.length) {
scope.toReleaseNamespace.viewType = 'rule';
AppUtil.showModal('#grayReleaseWithoutRulesTips');
}
} else { EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE, {
{ namespace: scope.toReleaseNamespace
namespace: scope.toReleaseNamespace })
})
}
}, function (result) { }, function (result) {
scope.releaseBtnDisabled = false; scope.releaseBtnDisabled = false;
...@@ -99,6 +76,46 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na ...@@ -99,6 +76,46 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
} }
function grayPublish() {
scope.releaseBtnDisabled = true;
ReleaseService.grayPublish(scope.appId, scope.env,
scope.toReleaseNamespace.parentNamespace.baseInfo.clusterName,
scope.toReleaseNamespace.baseInfo.namespaceName,
scope.toReleaseNamespace.baseInfo.clusterName,
scope.toReleaseNamespace.releaseTitle,
scope.releaseComment).then(
function (result) {
AppUtil.hideModal('#releaseModal');
toastr.success("灰度发布成功");
scope.releaseBtnDisabled = false;
//refresh item status
scope.toReleaseNamespace.branchItems.forEach(function (item, index) {
if (item.isDeleted) {
scope.toReleaseNamespace.branchItems.splice(index, 1);
} else {
item.isModified = false;
}
});
scope.toReleaseNamespace.itemModifiedCnt = 0;
//check rules
if (!scope.toReleaseNamespace.rules
|| !scope.toReleaseNamespace.rules.ruleItems
|| !scope.toReleaseNamespace.rules.ruleItems.length) {
scope.toReleaseNamespace.viewType = 'rule';
AppUtil.showModal('#grayReleaseWithoutRulesTips');
}
}, function (result) {
scope.releaseBtnDisabled = false;
toastr.error(AppUtil.errorMsg(result), "灰度发布失败");
});
}
function mergeAndPublish() { function mergeAndPublish() {
NamespaceBranchService.mergeAndReleaseBranch(scope.appId, NamespaceBranchService.mergeAndReleaseBranch(scope.appId,
......
...@@ -16,7 +16,11 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q ...@@ -16,7 +16,11 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
}, },
release: { release: {
method: 'POST', method: 'POST',
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/release' url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/releases'
},
gray_release: {
method: 'POST',
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/branches/:branchName/releases'
}, },
rollback: { rollback: {
method: 'PUT', method: 'PUT',
...@@ -42,6 +46,25 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q ...@@ -42,6 +46,25 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
return d.promise; return d.promise;
} }
function createGrayRelease(appId, env, clusterName, namespaceName, branchName, releaseTitle, comment) {
var d = $q.defer();
resource.gray_release({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
branchName:branchName
}, {
releaseTitle: releaseTitle,
releaseComment: comment
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
function findAllReleases(appId, env, clusterName, namespaceName, page, size) { function findAllReleases(appId, env, clusterName, namespaceName, page, size) {
var d = $q.defer(); var d = $q.defer();
resource.find_all_releases({ resource.find_all_releases({
...@@ -106,7 +129,8 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q ...@@ -106,7 +129,8 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
} }
return { return {
release: createRelease, publish: createRelease,
grayPublish: createGrayRelease,
findAllRelease: findAllReleases, findAllRelease: findAllReleases,
findActiveReleases: findActiveReleases, findActiveReleases: findActiveReleases,
compare: compare, compare: compare,
......
<section class="panel namespace-panel"> <section class="panel namespace-panel">
<!--public or link label--> <!--public or link label-->
<header class="row namespace-attribute-panel"> <header class="row namespace-attribute-panel">
<div class="text-center namespace-attribute-public"> <div class="text-center namespace-attribute-public">
......
...@@ -52,8 +52,9 @@ public class RetryableRestTemplateTest extends AbstractUnitTest { ...@@ -52,8 +52,9 @@ public class RetryableRestTemplateTest extends AbstractUnitTest {
@Before @Before
public void init() { public void init() {
socketTimeoutException.initCause(new SocketTimeoutException()); socketTimeoutException.initCause(new SocketTimeoutException());
httpHostConnectException httpHostConnectException
.initCause(new HttpHostConnectException(new HttpHost(serviceOne, 80), new ConnectException())); .initCause(new HttpHostConnectException(new ConnectTimeoutException(), new HttpHost(serviceOne, 80)));
connectTimeoutException.initCause(new ConnectTimeoutException()); connectTimeoutException.initCause(new ConnectTimeoutException());
} }
......
...@@ -2,8 +2,8 @@ package com.ctrip.framework.apollo.portal.controller; ...@@ -2,8 +2,8 @@ package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.openapi.entity.ConsumerToken; import com.ctrip.framework.apollo.openapi.entity.ConsumerToken;
import com.ctrip.framework.apollo.openapi.service.ConsumerService; import com.ctrip.framework.apollo.openapi.service.ConsumerService;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
......
...@@ -7,12 +7,12 @@ import com.ctrip.framework.apollo.core.ConfigConsts; ...@@ -7,12 +7,12 @@ import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.components.txtresolver.PropertyResolver; import com.ctrip.framework.apollo.portal.components.txtresolver.PropertyResolver;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceTextModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceTextModel;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs; import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
......
...@@ -7,7 +7,7 @@ import com.ctrip.framework.apollo.portal.AbstractUnitTest; ...@@ -7,7 +7,7 @@ import com.ctrip.framework.apollo.portal.AbstractUnitTest;
import com.ctrip.framework.apollo.portal.constant.PermissionType; import com.ctrip.framework.apollo.portal.constant.PermissionType;
import com.ctrip.framework.apollo.portal.entity.po.Permission; import com.ctrip.framework.apollo.portal.entity.po.Permission;
import com.ctrip.framework.apollo.portal.entity.po.Role; import com.ctrip.framework.apollo.portal.entity.po.Role;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.util.RoleUtils; import com.ctrip.framework.apollo.portal.util.RoleUtils;
......
...@@ -8,7 +8,7 @@ import com.ctrip.framework.apollo.portal.AbstractIntegrationTest; ...@@ -8,7 +8,7 @@ import com.ctrip.framework.apollo.portal.AbstractIntegrationTest;
import com.ctrip.framework.apollo.portal.entity.po.Permission; import com.ctrip.framework.apollo.portal.entity.po.Permission;
import com.ctrip.framework.apollo.portal.entity.po.Role; import com.ctrip.framework.apollo.portal.entity.po.Role;
import com.ctrip.framework.apollo.portal.entity.po.RolePermission; import com.ctrip.framework.apollo.portal.entity.po.RolePermission;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.entity.po.UserRole; import com.ctrip.framework.apollo.portal.entity.po.UserRole;
import com.ctrip.framework.apollo.portal.repository.PermissionRepository; import com.ctrip.framework.apollo.portal.repository.PermissionRepository;
import com.ctrip.framework.apollo.portal.repository.RolePermissionRepository; import com.ctrip.framework.apollo.portal.repository.RolePermissionRepository;
......
...@@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableMap; ...@@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.portal.AbstractUnitTest; import com.ctrip.framework.apollo.portal.AbstractUnitTest;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
import com.ctrip.framework.apollo.portal.service.ServerConfigService; import com.ctrip.framework.apollo.portal.service.ServerConfigService;
import org.junit.Before; import org.junit.Before;
......
...@@ -8,11 +8,8 @@ spring: ...@@ -8,11 +8,8 @@ spring:
logging: logging:
level: level:
org.springframework.cloud: 'DEBUG' org.springframework.cloud: 'DEBUG'
file: /opt/logs/${ctrip.appid}/apollo-portal.log file: /opt/logs/100003173/apollo-portal.log
ctrip:
appid: 100003173
apollo: apollo:
portal: portal:
envs: local envs: local
......
...@@ -186,6 +186,11 @@ ...@@ -186,6 +186,11 @@
<artifactId>apollo-sso-ctrip</artifactId> <artifactId>apollo-sso-ctrip</artifactId>
<version>1.1.0</version> <version>1.1.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.ctrip.framework.apollo-ctrip-service</groupId>
<artifactId>apollo-email-service</artifactId>
<version>1.0.0</version>
</dependency>
<!--third party --> <!--third party -->
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment