Consul-Proxy:使用netty實作快速服務注冊
注冊服務并提供郵件服務
一、背景
Springcloud+consul作為微服務的注冊已經見怪不怪了,試下也很流行,在我個人雲伺服器上,我也是這樣做的。
然而,我的雲伺服器記憶體比較小,很快記憶體就被cloud全家桶吃光了,沒辦法部署其他應用了,是以,我覺得将一些服務獨立出去,放棄cloud全家桶。
Consul-proxy使用netty+consul實作服務注冊,并提供了若幹簡單的注解實作了http的mapping映射處理。
簡單來說,沒錯,是因為窮,才有了這個元件。
本例就使用郵件發送服務作為示例使用consul-proxy。
二、Maven配置
要使用consul-proxy,隻需要加入下面依賴即可。
<dependency>
<groupId>cn.pomit</groupId>
<artifactId>consul-proxy</artifactId>
<version>1.3</version>
</dependency>
複制
但是要完整的運作,還是需要其他依賴的,比如netty和json相關的jar包。
如果想使用mybatis連接配接資料庫,還需要引入mybatis,這裡使用了mybatis-proxy工具,mybatis-proxy工具對mybatis做了簡單的封裝,友善在非spring環境下使用mybatis。
完整的依賴如下:
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.pomit</groupId>
<artifactId>consul-proxy-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consul-proxy-demo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<dbcp.version>2.4.0</dbcp.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.40</version>
</dependency>
<dependency>
<groupId>cn.pomit</groupId>
<artifactId>consul-proxy</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>cn.pomit.consulproxy.ConsulApp</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
複制
以上maven的配置中,除了依賴以外,還有shade插件的配置,友善打包成可執行jar包。
說到這裡,需要提一下的是,如果打包後運作報錯:java.lang.SecurityException: Invalid signature file digest for Manifest main attributes 異常,可以在<configuration> 節點下增加<filters>即可。
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>cn.pomit.consulproxy.ConsulApp</mainClass>
</transformer>
</transformers>
</configuration>
複制
三、使用方法
3.1 啟動類
啟動類要加上@EnableServer,指明被管理的handler,每個被管理的handler就相當于spring中的一個controller。
import cn.pomit.consul.ConsulProxyApplication;
import cn.pomit.consul.annotation.EnableServer;
import cn.pomit.consul.annotation.InitConfiguration;
import cn.pomit.consulproxy.config.MailConfiguration;
import cn.pomit.consulproxy.handler.EmailRestHandler;
@EnableServer(handler = { EmailRestHandler.class})
@InitConfiguration(configurations = { MailConfiguration.class })
public class ConsulApp {
public static void main(String[] args) {
ConsulProxyApplication.run(ConsulApp.class, args);
}
}
複制
3.2 郵件服務的配置讀取
consul-proxy工具可以自動讀取解析@InitConfiguration注解,并調用注解指定的配置類的initConfiguration方法,這個方法要聲明為static方法,并有Properties 參數,consul-proxy會自動将程式的配置傳遞給這個initConfiguration方法。
這裡将Properties 傳遞給MailConfiguration進行初始化,友善讀取配置。
MailConfiguration :
import java.util.Properties;
public class MailConfiguration {
private static MailConfiguration instance = null;
private String mailUserName;
private String mailPassword;
private String mailFrom;
private String mailFromName;
private Properties mailproperties;
public static void initConfiguration(Properties properties) {
instance = new MailConfiguration();
instance.mailUserName = properties.getProperty("mail.username");
instance.mailPassword = properties.getProperty("mail.password");
instance.mailFrom = properties.getProperty("mail.from");
instance.mailFromName = properties.getProperty("mail.fromName");
instance.mailproperties = new Properties();
instance.mailproperties.put("mail.smtp.host", properties.getProperty("mail.host"));
instance.mailproperties.put("mail.smtp.port", properties.getProperty("mail.port"));
instance.mailproperties.put("mail.smtp.auth", properties.getProperty("mail.properties.mail.smtp.auth"));
instance.mailproperties.put("mail.smtp.starttls.enable",
properties.getProperty("mail.properties.mail.smtp.starttls.enable"));
instance.mailproperties.put("mail.smtp.ssl.enable",
properties.getProperty("mail.properties.mail.smtp.ssl.enable"));
instance.mailproperties.put("mail.smtp.starttls.required",
properties.getProperty("mail.properties.mail.smtp.starttls.required"));
}
public static MailConfiguration getInstance() {
return instance;
}
public String getMailUserName() {
return mailUserName;
}
public void setMailUserName(String mailUserName) {
this.mailUserName = mailUserName;
}
public String getMailPassword() {
return mailPassword;
}
public void setMailPassword(String mailPassword) {
this.mailPassword = mailPassword;
}
public String getMailFrom() {
return mailFrom;
}
public void setMailFrom(String mailFrom) {
this.mailFrom = mailFrom;
}
public Properties getMailproperties() {
return mailproperties;
}
public void setMailproperties(Properties mailproperties) {
this.mailproperties = mailproperties;
}
public String getMailFromName() {
return mailFromName;
}
public void setMailFromName(String mailFromName) {
this.mailFromName = mailFromName;
}
}
複制
3.3 Handler處理類
下面是EmailRestHandler類,使用json處理相關資料,發送郵件,傳回json結果。
EmailRestHandler:
import java.nio.charset.Charset;
import com.alibaba.fastjson.JSONObject;
import cn.pomit.consul.annotation.Mapping;
import cn.pomit.consul.handler.resource.AbstractResourceHandler;
import cn.pomit.consul.http.HttpRequestMessage;
import cn.pomit.consul.http.HttpResponseMessage;
import cn.pomit.consul.http.res.ResCode;
import cn.pomit.consul.http.res.ResType;
import cn.pomit.consulproxy.dto.ResultCode;
import cn.pomit.consulproxy.dto.ResultModel;
import cn.pomit.consulproxy.dto.mail.Email;
import cn.pomit.consulproxy.service.mail.MailService;
public class EmailRestHandler extends AbstractResourceHandler {
@Mapping(value = "/email/code")
public HttpResponseMessage findUser(HttpRequestMessage httpRequestMessage) {
ResultModel rm = null;
try {
String content = httpRequestMessage.getBody().toString(Charset.defaultCharset());
Email email = JSONObject.parseObject(content, Email.class);
if (email == null) {
rm = new ResultModel(ResultCode.CODE_00003);
} else {
MailService.sendHtmlMail(email);
rm = ResultModel.ok();
}
} catch (Exception e) {
e.printStackTrace();
rm = ResultModel.error(e.getMessage());
}
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.setResCode(ResCode.OK.getValue());
httpResponseMessage.setResType(ResType.JSON.getValue());
httpResponseMessage.setMessage(JSONObject.toJSONString(rm));
return httpResponseMessage;
}
@Mapping(value = "/health")
public HttpResponseMessage add(HttpRequestMessage httpRequestMessage) throws Exception {
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
httpResponseMessage.setResCode(ResCode.OK.getValue());
httpResponseMessage.setResType(ResType.JSON.getValue());
httpResponseMessage.setMessage("echo");
return httpResponseMessage;
}
}
複制
3.4 Service處理邏輯層
由于不是spring環境,Service每次使用需要自己new一個對象了。這裡的MailService 是調用javax.mail的邏輯。
MailService :
import java.util.Date;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import cn.pomit.consulproxy.config.MailConfiguration;
import cn.pomit.consulproxy.dto.mail.Email;
public class MailService {
/**
* 發送Html郵件
*
* @throws Exception
*/
public static void sendHtmlMail(Email email) throws Exception {
// 根據郵件會話屬性和密碼驗證器構造一個發送郵件的session
Session sendMailSession = Session.getDefaultInstance(MailConfiguration.getInstance().getMailproperties(),
new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(MailConfiguration.getInstance().getMailUserName(),
MailConfiguration.getInstance().getMailPassword());
}
});
// 根據session建立一個郵件消息
Message mailMessage = new MimeMessage(sendMailSession);
// 設定郵件消息的發送者
mailMessage.setFrom(new InternetAddress(MailConfiguration.getInstance().getMailFrom(),
MailConfiguration.getInstance().getMailFromName()));
// 建立郵件的接收者位址,并設定到郵件消息中
mailMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(email.getTo()));
// 設定郵件消息的主題
mailMessage.setSubject(email.getSubject());
// 設定郵件消息發送的時間
mailMessage.setSentDate(new Date());
// MiniMultipart類是一個容器類,包含MimeBodyPart類型的對象
Multipart mainPart = new MimeMultipart();
// 建立一個包含HTML内容的MimeBodyPart
BodyPart html = new MimeBodyPart();
// 設定HTML内容
html.setContent(email.getContent(), "text/html; charset=utf-8");
mainPart.addBodyPart(html);
// 将MiniMultipart對象設定為郵件内容
mailMessage.setContent(mainPart);
// 發送郵件
Transport.send(mailMessage);
}
}
複制
3.5 過程中使用到的實體
Email :
public class Email {
String to;
String subject;
String content;
boolean html = true;
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public boolean isHtml() {
return html;
}
public void setHtml(boolean html) {
this.html = html;
}
}
複制
ResultCode:
public enum ResultCode {
/**
* 通用
*/
CODE_00000("00000", "操作成功"),
CODE_00001("00001", "請求失敗"),
CODE_00002("00002", "未授權的請求"),
CODE_00003("00003", "非法的參數字段"),
CODE_00004("00004", "異常抛出"),
CODE_00005("00005", "權限不足"),
CODE_00006("00006", "分頁limit參數錯誤"),
CODE_00007("00007", "分頁offset參數錯誤"),
CODE_00009("00009", "未登入或登入狀态已失效"),
CODE_00010("00010", "資料已存在"),
CODE_00011("00011", "資料不存在"),
CODE_00012("00012", "參數缺失"),
CODE_00013("00013", "系統維護中"),
CODE_00014("00014", "登入失敗"),
CODE_00015("00015", "token失效"),
CODE_00016("00016", "簽名錯誤"),
CODE_99999("99999", "簽名無效");
private String code;
private String desc;
ResultCode(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
/**
* 根據code比對枚舉
*
* @param code
* @return
*/
public static ResultCode getResultCodeByCode(String code) {
for (ResultCode resultCode : ResultCode.values()) {
if (code.equals(resultCode.getCode())) {
return resultCode;
}
}
return null;
}
public static ResultCode getResultCodeByDesc(String desc) {
for (ResultCode resultCode : ResultCode.values()) {
if (desc.equals(resultCode.getDesc())) {
return resultCode;
}
}
return null;
}
}
複制
ResultModel :
public class ResultModel {
private String errorCode;
private String message;
private Object remark;
private Object data;
public ResultModel(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}
public ResultModel() {
}
public ResultModel(String errorCode, String message, Object data) {
this.errorCode = errorCode;
this.message = message;
this.data = data;
}
public ResultModel(ResultCode resultCodeEnum, Object data) {
this.errorCode = resultCodeEnum.getCode();
this.message = resultCodeEnum.getDesc();
this.data = data;
}
public ResultModel(ResultCode resultCodeEnum, Object data, Object remark) {
this.errorCode = resultCodeEnum.getCode();
this.message = resultCodeEnum.getDesc();
this.data = data;
this.remark = remark;
}
public ResultModel(ResultCode resultCodeEnum) {
this.errorCode = resultCodeEnum.getCode();
this.message = resultCodeEnum.getDesc();
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static ResultModel ok() {
return new ResultModel(ResultCode.CODE_00000);
}
public static ResultModel ok(Object data) {
return new ResultModel(ResultCode.CODE_00000, data);
}
public static ResultModel unAuth() {
return new ResultModel(ResultCode.CODE_00002);
}
public static ResultModel unAuth(Object data) {
return new ResultModel(ResultCode.CODE_00002, data);
}
public static ResultModel error(String message) {
return new ResultModel(ResultCode.CODE_00001.getCode(), message);
}
public Object getRemark() {
return remark;
}
public void setRemark(Object remark) {
this.remark = remark;
}
}
複制
四、配置檔案
4.1 application.properties
在classpath下添加application.properties
spring.profiles.active=loc
application.name=consulProxy
application.port=8999
複制
這個指定了帶環境的配置檔案可以讀取classpath下的application-loc.properties
application-loc.properties:
consul.host=127.0.0.1
consul.port=8500
#consul.instanceId=cunsul-proxy-8888
#consul.scheme=https
#consul.healthCheckUrl=/health
consul.healthCheckPath=/health
#consul.healthCheckInterval=10s
#consul.healthCheckTimeout=100s
consul.preferIpAddress=true
mail.host=smtp.qq.com
[email protected]
mail.password=xxxx
mail.port=465
mail.properties.mail.smtp.auth=true
mail.properties.mail.smtp.starttls.enable=true
mail.properties.mail.smtp.ssl.enable=true
mail.properties.mail.smtp.starttls.required=true
[email protected]
mail.fromName=Admin
複制
如果不帶環境,直接把application-loc.properties的配置寫在application.properties也是一樣的。
這裡面,以mail.開發的配置,是我們在MailConfiguration裡面需要的自定義配置。其他配置為consul-proxy需要的配置。
4.2 log4j.properties
使用log4j,log4j的配置檔案寫在classpath下即可:
# DEBUG,INFO,WARN,ERROR,FATAL
LOG_LEVEL=DEBUG
log4j.rootLogger=${LOG_LEVEL},CONSOLE,FILE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Encoding=utf-8
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
#log4j.appender.CONSOLE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %C{8}@(%F:%L):%m%n
log4j.appender.CONSOLE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %C{1}@(%F:%L):%m%n
log4j.appender.FILE=org.apache.log4j.DailyRollingFileAppender
log4j.appender.FILE.File=logs/workorder.log
log4j.appender.FILE.Encoding=utf-8
log4j.appender.FILE.DatePattern='.'yyyy-MM-dd
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
#log4j.appender.FILE.layout=org.apache.log4j.HTMLLayout
log4j.appender.FILE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH\:mm\:ss} %C{8}@(%F\:%L)\:%m%n
複制