This commit is contained in:
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.boot.ClassFilter.FilterEntry;
|
||||
import com.wentch.redkale.boot.*;
|
||||
import static com.wentch.redkale.boot.NodeServer.LINE_SEPARATOR;
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import com.wentch.redkale.util.AnyValue.DefaultAnyValue;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
/**
|
||||
* < server protocol="SOCKS" host="0.0.0.0" port="1080" bindaddr="外网IP"> < /server>
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@NodeProtocol({"SOCKS"})
|
||||
public class NodeSocksServer extends NodeServer {
|
||||
|
||||
private final SocksServer socksServer;
|
||||
|
||||
public NodeSocksServer(Application application, AnyValue serconf) {
|
||||
super(application, application.getResourceFactory().createChild(), createServer(application, serconf));
|
||||
this.socksServer = (SocksServer) server;
|
||||
}
|
||||
|
||||
private static Server createServer(Application application, AnyValue serconf) {
|
||||
return new SocksServer(application.getStartTime(), application.getWatchFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getSocketAddress() {
|
||||
return socksServer == null ? null : socksServer.getSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Servlet> createServletClassFilter() {
|
||||
return createClassFilter(null, null, SocksServlet.class, null, "servlets", "servlet");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadServlet(ClassFilter<? extends Servlet> servletFilter) throws Exception {
|
||||
if (socksServer != null) loadSocksServlet(this.nodeConf.getAnyValue("servlets"), servletFilter);
|
||||
}
|
||||
|
||||
protected void loadSocksServlet(final AnyValue conf, ClassFilter<? extends Servlet> filter) throws Exception {
|
||||
final StringBuilder sb = logger.isLoggable(Level.FINE) ? new StringBuilder() : null;
|
||||
final String threadName = "[" + Thread.currentThread().getName() + "] ";
|
||||
for (FilterEntry<? extends Servlet> en : filter.getFilterEntrys()) {
|
||||
Class<SocksServlet> clazz = (Class<SocksServlet>) en.getType();
|
||||
if (Modifier.isAbstract(clazz.getModifiers())) continue;
|
||||
final SocksServlet servlet = clazz.newInstance();
|
||||
factory.inject(servlet);
|
||||
DefaultAnyValue servletConf = (DefaultAnyValue) en.getProperty();
|
||||
this.socksServer.addSocksServlet(servlet, servletConf);
|
||||
if (sb != null) sb.append(threadName).append(" Loaded ").append(clazz.getName()).append(" --> ").append(servletConf).append(LINE_SEPARATOR);
|
||||
}
|
||||
if (sb != null && sb.length() > 0) logger.log(Level.FINE, sb.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@AutoLoad(false)
|
||||
public class SocksConnectServlet extends SocksServlet {
|
||||
|
||||
private InetSocketAddress bindAddress;
|
||||
|
||||
private byte[] bindAddressBytes = new byte[0];
|
||||
|
||||
@Override
|
||||
public void init(Context context, AnyValue config) {
|
||||
if (config == null) {
|
||||
this.bindAddress = new InetSocketAddress(Utility.localInetAddress(), context.getServerAddress().getPort());
|
||||
} else {
|
||||
this.bindAddress = new InetSocketAddress(config.getValue("bindaddr", Utility.localInetAddress().getHostAddress()), context.getServerAddress().getPort());
|
||||
}
|
||||
Logger logger = context.getLogger();
|
||||
if (logger.isLoggable(Level.INFO)) logger.info("[" + Thread.currentThread().getName() + "] bindAddress = " + bindAddress);
|
||||
ByteBuffer bb;
|
||||
InetAddress addr = bindAddress.getAddress();
|
||||
if (addr instanceof Inet6Address) {
|
||||
bb = ByteBuffer.allocate(1 + 16 + 2);
|
||||
bb.put((byte) 0x04);
|
||||
} else {
|
||||
bb = ByteBuffer.allocate(1 + 4 + 2);
|
||||
bb.put((byte) 0x01);
|
||||
}
|
||||
bb.put(addr.getAddress());
|
||||
bb.putChar((char) bindAddress.getPort());
|
||||
bb.flip();
|
||||
this.bindAddressBytes = bb.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(SocksRequest request, SocksResponse response) throws IOException {
|
||||
response.getContext().submit(new SocksRunner(response.getContext(), response.removeChannel(), bindAddressBytes));
|
||||
response.finish(true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class SocksPrepareServlet extends PrepareServlet<SocksRequest, SocksResponse> {
|
||||
|
||||
private SocksServlet socksServlet = new SocksConnectServlet();
|
||||
|
||||
private SocksProxyServlet proxyServlet = new SocksProxyServlet();
|
||||
|
||||
public SocksPrepareServlet() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Context context, AnyValue config) {
|
||||
if (socksServlet != null) socksServlet.init(context, socksServlet.conf == null ? config : socksServlet.conf);
|
||||
}
|
||||
|
||||
public void setSocksServlet(SocksServlet servlet, AnyValue conf) {
|
||||
servlet.conf = conf;
|
||||
if (servlet != null) this.socksServlet = servlet;
|
||||
}
|
||||
|
||||
//
|
||||
@Override
|
||||
public void execute(SocksRequest request, SocksResponse response) throws IOException {
|
||||
if (request.isHttp()) {
|
||||
proxyServlet.execute(request, response);
|
||||
} else {
|
||||
socksServlet.execute(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
|
||||
/**
|
||||
* 正向代理
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@AutoLoad(false)
|
||||
public final class SocksProxyServlet extends SocksServlet {
|
||||
|
||||
protected static final byte[] LINE = new byte[]{'\r', '\n'};
|
||||
|
||||
@Override
|
||||
public void execute(SocksRequest request, SocksResponse response) throws IOException {
|
||||
response.skipHeader();
|
||||
if ("CONNECT".equalsIgnoreCase(request.getMethod())) {
|
||||
connect(request, response);
|
||||
return;
|
||||
}
|
||||
String url = request.getRequestURI();
|
||||
url = url.substring(url.indexOf("://") + 3);
|
||||
url = url.substring(url.indexOf('/'));
|
||||
final ByteBuffer buffer = response.getContext().pollBuffer();
|
||||
buffer.put((request.getMethod() + " " + url + " HTTP/1.1\r\n").getBytes());
|
||||
for (String header : request.getHeaderNames()) {
|
||||
if (!header.startsWith("Proxy-")) {
|
||||
buffer.put((header + ": " + request.getHeader(header) + "\r\n").getBytes());
|
||||
}
|
||||
}
|
||||
if (request.getHost() != null) {
|
||||
buffer.put(("Host: " + request.getHost() + "\r\n").getBytes());
|
||||
}
|
||||
if (request.getContentType() != null) {
|
||||
buffer.put(("Content-Type: " + request.getContentType() + "\r\n").getBytes());
|
||||
}
|
||||
if (request.getContentLength() > 0) {
|
||||
buffer.put(("Content-Length: " + request.getContentLength() + "\r\n").getBytes());
|
||||
}
|
||||
buffer.put(LINE);
|
||||
buffer.flip();
|
||||
final AsyncConnection remote = AsyncConnection.create("TCP", request.getHostSocketAddress(), 6, 6);
|
||||
remote.write(buffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
if (buffer.hasRemaining()) {
|
||||
remote.write(buffer, attachment, this);
|
||||
return;
|
||||
}
|
||||
response.getContext().offerBuffer(buffer);
|
||||
new ProxyCompletionHandler(remote, request, response).completed(0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
response.getContext().offerBuffer(buffer);
|
||||
response.finish(true);
|
||||
try {
|
||||
remote.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void connect(SocksRequest request, SocksResponse response) throws IOException {
|
||||
final InetSocketAddress remoteAddress = request.parseSocketAddress();
|
||||
final AsyncConnection remote = remoteAddress.getPort() == 443
|
||||
? AsyncConnection.create(Utility.createDefaultSSLSocket(remoteAddress)) : AsyncConnection.create("TCP", remoteAddress, 6, 6);
|
||||
final ByteBuffer buffer0 = response.getContext().pollBuffer();
|
||||
buffer0.put("HTTP/1.1 200 Connection established\r\nConnection: close\r\n\r\n".getBytes());
|
||||
buffer0.flip();
|
||||
response.sendBody(buffer0, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
new ProxyCompletionHandler(remote, request, response).completed(0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
response.finish(true);
|
||||
try {
|
||||
remote.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static class ProxyCompletionHandler implements CompletionHandler<Integer, Void> {
|
||||
|
||||
private AsyncConnection remote;
|
||||
|
||||
private SocksRequest request;
|
||||
|
||||
private SocksResponse response;
|
||||
|
||||
public ProxyCompletionHandler(AsyncConnection remote, SocksRequest request, SocksResponse response) {
|
||||
this.remote = remote;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed(Integer result0, Void v0) {
|
||||
final ByteBuffer rbuffer = request.getContext().pollBuffer();
|
||||
remote.read(rbuffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
rbuffer.flip();
|
||||
CompletionHandler parent = this;
|
||||
response.sendBody(rbuffer.duplicate().asReadOnlyBuffer(), null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
rbuffer.clear();
|
||||
remote.read(rbuffer, attachment, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
parent.failed(exc, attachment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
response.getContext().offerBuffer(rbuffer);
|
||||
response.finish(true);
|
||||
try {
|
||||
remote.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final ByteBuffer qbuffer = request.getContext().pollBuffer();
|
||||
request.getChannel().read(qbuffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
qbuffer.flip();
|
||||
CompletionHandler parent = this;
|
||||
remote.write(qbuffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
qbuffer.clear();
|
||||
request.getChannel().read(qbuffer, null, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
parent.failed(exc, attachment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
response.getContext().offerBuffer(qbuffer);
|
||||
response.finish(true);
|
||||
try {
|
||||
remote.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void v) {
|
||||
response.finish(true);
|
||||
try {
|
||||
remote.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.net.http.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class SocksRequest extends HttpRequest {
|
||||
|
||||
private boolean http;
|
||||
|
||||
private short requestid;
|
||||
|
||||
protected SocksRequest(HttpContext context) {
|
||||
super(context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int readHeader(ByteBuffer buffer) {
|
||||
if (buffer.get(0) > 0x05 && buffer.remaining() > 3) {
|
||||
this.http = true;
|
||||
return super.readHeader(buffer);
|
||||
}
|
||||
this.http = false;
|
||||
if (buffer.get() != 0x05) return -1;
|
||||
if (buffer.get() != 0x01) return -1;
|
||||
if (buffer.get() != 0x00) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected InetSocketAddress parseSocketAddress() {
|
||||
return HttpRequest.parseSocketAddress(getRequestURI());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InetSocketAddress getHostSocketAddress() {
|
||||
return super.getHostSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AsyncConnection getChannel() {
|
||||
return super.getChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int readBody(ByteBuffer buffer) {
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepare() {
|
||||
super.prepare();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recycle() {
|
||||
this.requestid = 0;
|
||||
this.http = false;
|
||||
super.recycle();
|
||||
}
|
||||
|
||||
public short getRequestid() {
|
||||
return requestid;
|
||||
}
|
||||
|
||||
public void setRequestid(short requestid) {
|
||||
this.requestid = requestid;
|
||||
}
|
||||
|
||||
public boolean isHttp() {
|
||||
return http;
|
||||
}
|
||||
|
||||
public void setHttp(boolean http) {
|
||||
this.http = http;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.net.http.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class SocksResponse extends HttpResponse<SocksRequest> {
|
||||
|
||||
protected SocksResponse(Context context, SocksRequest request) {
|
||||
super(context, request, (String[][]) null, (String[][]) null, null);
|
||||
}
|
||||
|
||||
public static ObjectPool<Response> createPool(AtomicLong creatCounter, AtomicLong cycleCounter, int max, Creator<Response> creator) {
|
||||
return new ObjectPool<>(creatCounter, cycleCounter, max, creator, (x) -> ((SocksResponse) x).prepare(), (x) -> ((SocksResponse) x).recycle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncConnection removeChannel() {
|
||||
return super.removeChannel();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class SocksRunner implements Runnable {
|
||||
|
||||
private final AsyncConnection channel;
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
private final boolean finest;
|
||||
|
||||
private final Context context;
|
||||
|
||||
private final byte[] bindAddressBytes;
|
||||
|
||||
private ByteBuffer buffer;
|
||||
|
||||
protected boolean closed = false;
|
||||
|
||||
private InetSocketAddress remoteAddress;
|
||||
|
||||
private AsyncConnection remoteChannel;
|
||||
|
||||
public SocksRunner(Context context, AsyncConnection channel, final byte[] bindAddressBytes) {
|
||||
this.context = context;
|
||||
this.logger = context.getLogger();
|
||||
this.finest = this.context.getLogger().isLoggable(Level.FINEST);
|
||||
this.channel = channel;
|
||||
this.buffer = context.pollBuffer();
|
||||
this.bindAddressBytes = bindAddressBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ask();
|
||||
} catch (Exception e) {
|
||||
closeRunner(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void ask() {
|
||||
buffer.putChar((char) 0x0500);
|
||||
buffer.flip();
|
||||
this.channel.write(buffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
if (buffer.hasRemaining()) {
|
||||
channel.write(buffer, null, this);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
connect();
|
||||
} catch (Exception e) {
|
||||
closeRunner(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
closeRunner(exc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
buffer.clear();
|
||||
this.channel.read(buffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
buffer.flip();
|
||||
if (buffer.getChar() != 0x0501) {
|
||||
if (finest) logger.finest("connect header not 0x0501");
|
||||
closeRunner(null);
|
||||
return;
|
||||
}
|
||||
char addrtype = buffer.getChar(); //0x0001 - 4 ; 0x0003 - x ; 0x0004 - 16
|
||||
try {
|
||||
byte[] bytes = new byte[(addrtype == 0x0003) ? (buffer.get() & 0xff) : (addrtype * 4)];
|
||||
buffer.get(bytes);
|
||||
remoteAddress = new InetSocketAddress((addrtype == 0x0003) ? InetAddress.getByName(new String(bytes)) : InetAddress.getByAddress(bytes), buffer.getChar());
|
||||
} catch (UnknownHostException e) {
|
||||
failed(e, attachment);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
remoteChannel = AsyncConnection.create("TCP", remoteAddress, 6, 6);
|
||||
buffer.clear();
|
||||
buffer.putChar((char) 0x0500);
|
||||
buffer.put((byte) 0x00); //rsv
|
||||
buffer.put(bindAddressBytes);
|
||||
buffer.flip();
|
||||
channel.write(buffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
if (buffer.hasRemaining()) {
|
||||
channel.write(buffer, null, this);
|
||||
return;
|
||||
}
|
||||
stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
closeRunner(exc);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
buffer.clear();
|
||||
buffer.putChar((char) 0x0504);
|
||||
if (finest) logger.log(Level.FINEST, remoteAddress + " remote connect error", e);
|
||||
channel.write(buffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
if (buffer.hasRemaining()) {
|
||||
channel.write(buffer, null, this);
|
||||
return;
|
||||
}
|
||||
closeRunner(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
closeRunner(exc);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
closeRunner(exc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void stream() {
|
||||
new StreamCompletionHandler(channel, remoteChannel).completed(0, null);
|
||||
new StreamCompletionHandler(remoteChannel, channel).completed(0, null);
|
||||
}
|
||||
|
||||
public void closeRunner(final Throwable e) {
|
||||
if (closed) return;
|
||||
synchronized (this) {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
try {
|
||||
channel.close();
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
context.offerBuffer(buffer);
|
||||
buffer = null;
|
||||
if (e != null && finest) {
|
||||
logger.log(Level.FINEST, "close socks channel by error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StreamCompletionHandler implements CompletionHandler<Integer, Void> {
|
||||
|
||||
private final AsyncConnection conn1;
|
||||
|
||||
private final AsyncConnection conn2;
|
||||
|
||||
private final ByteBuffer rbuffer;
|
||||
|
||||
public StreamCompletionHandler(AsyncConnection conn1, AsyncConnection conn2) {
|
||||
this.conn1 = conn1;
|
||||
this.conn2 = conn2;
|
||||
this.rbuffer = context.pollBuffer();
|
||||
this.rbuffer.flip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed(Integer result0, Void v0) {
|
||||
final CompletionHandler self = this;
|
||||
if (rbuffer.hasRemaining()) {
|
||||
conn2.write(rbuffer, null, self);
|
||||
return;
|
||||
}
|
||||
rbuffer.clear();
|
||||
conn1.read(rbuffer, null, new CompletionHandler<Integer, Void>() {
|
||||
|
||||
@Override
|
||||
public void completed(Integer result, Void attachment) {
|
||||
rbuffer.flip();
|
||||
conn2.write(rbuffer, attachment, self);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void attachment) {
|
||||
self.failed(exc, attachment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable exc, Void v) {
|
||||
context.offerBuffer(rbuffer);
|
||||
conn1.dispose();
|
||||
conn2.dispose();
|
||||
if (finest) logger.log(Level.FINEST, "StreamCompletionHandler closed", exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.net.http.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import com.wentch.redkale.watch.*;
|
||||
import java.nio.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class SocksServer extends Server {
|
||||
|
||||
public SocksServer() {
|
||||
this(System.currentTimeMillis(), null);
|
||||
}
|
||||
|
||||
public SocksServer(long serverStartTime, final WatchFactory watch) {
|
||||
super(serverStartTime, "TCP", new SocksPrepareServlet(), watch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(AnyValue config) throws Exception {
|
||||
super.init(config);
|
||||
}
|
||||
|
||||
public void addSocksServlet(SocksServlet servlet, AnyValue conf) {
|
||||
((SocksPrepareServlet) this.prepare).setSocksServlet(servlet, conf);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Context createContext() {
|
||||
final int port = this.address.getPort();
|
||||
AtomicLong createBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SOCKS_" + port + ".Buffer.creatCounter");
|
||||
AtomicLong cycleBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SOCKS_" + port + ".Buffer.cycleCounter");
|
||||
int rcapacity = Math.max(this.capacity, 8 * 1024);
|
||||
ObjectPool<ByteBuffer> bufferPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, this.bufferPoolSize,
|
||||
(Object... params) -> ByteBuffer.allocateDirect(rcapacity), null, (e) -> {
|
||||
if (e == null || e.isReadOnly() || e.capacity() != rcapacity) return false;
|
||||
e.clear();
|
||||
return true;
|
||||
});
|
||||
AtomicLong createResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SOCKS_" + port + ".Response.creatCounter");
|
||||
AtomicLong cycleResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SOCKS_" + port + ".Response.cycleCounter");
|
||||
ObjectPool<Response> responsePool = SocksResponse.createPool(createResponseCounter, cycleResponseCounter, this.responsePoolSize, null);
|
||||
HttpContext localcontext = new HttpContext(this.serverStartTime, this.logger, executor, rcapacity, bufferPool, responsePool,
|
||||
this.maxbody, this.charset, this.address, this.prepare, this.watch, this.readTimeoutSecond, this.writeTimeoutSecond, "");
|
||||
responsePool.setCreator((Object... params) -> new SocksResponse(localcontext, new SocksRequest(localcontext)));
|
||||
return localcontext;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.socks;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public abstract class SocksServlet implements Servlet<SocksRequest, SocksResponse> {
|
||||
|
||||
AnyValue conf; //当前Servlet的配置
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
return obj != null && obj.getClass() == this.getClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return this.getClass().hashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.service.apns;
|
||||
|
||||
import com.wentch.redkale.convert.json.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class ApnsMessage {
|
||||
|
||||
public static final int PRIORITY_IMMEDIATELY = 10;
|
||||
|
||||
public static final int PRIORITY_A_TIME = 5;
|
||||
|
||||
private ApnsPayload payload;
|
||||
|
||||
private int expiredate;
|
||||
|
||||
private int priority = PRIORITY_IMMEDIATELY;
|
||||
|
||||
private int identifier;
|
||||
|
||||
private String token;
|
||||
|
||||
public ApnsMessage() {
|
||||
}
|
||||
|
||||
public ApnsMessage(String token, ApnsPayload payload) {
|
||||
this(token, payload, 0);
|
||||
}
|
||||
|
||||
public ApnsMessage(String token, ApnsPayload payload, int expiredate) {
|
||||
this(token, payload, expiredate, PRIORITY_IMMEDIATELY);
|
||||
}
|
||||
|
||||
public ApnsMessage(String token, ApnsPayload payload, int expiredate, int priority) {
|
||||
this.token = token;
|
||||
this.payload = payload;
|
||||
this.expiredate = expiredate;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public int getExpiredate() {
|
||||
return expiredate;
|
||||
}
|
||||
|
||||
public void setExpiredate(int expiredate) {
|
||||
this.expiredate = expiredate;
|
||||
}
|
||||
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public ApnsPayload getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public void setPayload(ApnsPayload payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public int getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public void setIdentifier(int identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonFactory.root().getConvert().convertTo(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.service.apns;
|
||||
|
||||
import com.wentch.redkale.convert.json.*;
|
||||
import java.util.*;
|
||||
import java.util.regex.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class ApnsPayload {
|
||||
|
||||
private static final Pattern regex = Pattern.compile("\"");
|
||||
|
||||
//----------------------- alert ---------------------------------
|
||||
private String alertTitle;
|
||||
|
||||
private String alertBody;
|
||||
|
||||
private String alertTitleLocKey;
|
||||
|
||||
private String[] alertTitleLocArgs;
|
||||
|
||||
private String alertActionLocKey;
|
||||
|
||||
private String alertLocKey;
|
||||
|
||||
private String[] alertLocArgs;
|
||||
|
||||
private String alertLaunchImage;
|
||||
|
||||
//--------------------------------------------------------
|
||||
private int contentAvailable;
|
||||
|
||||
private String alert;
|
||||
|
||||
private int badge;
|
||||
|
||||
private String sound;
|
||||
|
||||
private final Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
public ApnsPayload() {
|
||||
}
|
||||
|
||||
public ApnsPayload(String alert, int badge) {
|
||||
this.alert = alert;
|
||||
this.badge = badge;
|
||||
}
|
||||
|
||||
public ApnsPayload(String title, String body, int badge) {
|
||||
this.alertTitle = title;
|
||||
this.alertBody = body;
|
||||
this.badge = badge;
|
||||
}
|
||||
|
||||
public void putAttribute(String name, Object value) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
public void removeAttribute(String name) {
|
||||
attributes.remove(name);
|
||||
}
|
||||
|
||||
public <T> T getAttribute(String name) {
|
||||
return (T) attributes.get(name);
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, Object> map) {
|
||||
if (map != null) attributes.putAll(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder alertsb = new StringBuilder();
|
||||
if (alert != null) {
|
||||
alertsb.append('"').append(regex.matcher(alert).replaceAll("\\\"")).append('"');
|
||||
} else {
|
||||
alertsb.append('{');
|
||||
if (alertTitle != null) {
|
||||
if (alertsb.length() > 1) alertsb.append(',');
|
||||
alertsb.append("\"title\":\"").append(regex.matcher(alertTitle).replaceAll("\\\"")).append('"');
|
||||
}
|
||||
if (alertBody != null) {
|
||||
if (alertsb.length() > 1) alertsb.append(',');
|
||||
alertsb.append("\"body\":\"").append(regex.matcher(alertBody).replaceAll("\\\"")).append('"');
|
||||
}
|
||||
if (alertTitleLocKey != null) {
|
||||
if (alertsb.length() > 1) alertsb.append(',');
|
||||
alertsb.append("\"title-loc-key\":\"").append(regex.matcher(alertTitleLocKey).replaceAll("\\\"")).append('"');
|
||||
}
|
||||
if (alertTitleLocArgs != null && alertTitleLocArgs.length > 0) {
|
||||
if (alertsb.length() > 1) alertsb.append(',');
|
||||
alertsb.append("\"title-loc-args\":[");
|
||||
boolean first = true;
|
||||
for (String str : alertTitleLocArgs) {
|
||||
if (!first) alertsb.append(',');
|
||||
alertsb.append('"').append(regex.matcher(str).replaceAll("\\\"")).append('"');
|
||||
first = false;
|
||||
}
|
||||
alertsb.append(']');
|
||||
}
|
||||
if (alertActionLocKey != null) {
|
||||
if (alertsb.length() > 1) alertsb.append(',');
|
||||
alertsb.append("\"action-loc-key\":\"").append(regex.matcher(alertActionLocKey).replaceAll("\\\"")).append('"');
|
||||
}
|
||||
if (alertLocKey != null) {
|
||||
if (alertsb.length() > 1) alertsb.append(',');
|
||||
alertsb.append("\"loc-key\":\"").append(regex.matcher(alertLocKey).replaceAll("\\\"")).append('"');
|
||||
}
|
||||
if (alertLocArgs != null && alertLocArgs.length > 0) {
|
||||
if (alertsb.length() > 1) alertsb.append(',');
|
||||
alertsb.append("\"loc-args\":[");
|
||||
boolean first = true;
|
||||
for (String str : alertLocArgs) {
|
||||
if (!first) alertsb.append(',');
|
||||
alertsb.append('"').append(regex.matcher(str).replaceAll("\\\"")).append('"');
|
||||
first = false;
|
||||
}
|
||||
alertsb.append(']');
|
||||
}
|
||||
if (alertLaunchImage != null) {
|
||||
if (alertsb.length() > 1) alertsb.append(',');
|
||||
alertsb.append("\"launch-image\":\"").append(regex.matcher(alertLaunchImage).replaceAll("\\\"")).append('"');
|
||||
}
|
||||
alertsb.append('}');
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("{\"aps\":{\"alert\":").append(alertsb);
|
||||
if (badge > 0) sb.append(",\"badge\":").append(badge);
|
||||
if (contentAvailable > 0) sb.append(",\"content-available\":").append(contentAvailable);
|
||||
if (sound != null) sb.append(",\"sound\":\"").append(sound).append('"');
|
||||
sb.append("}");
|
||||
if (attributes.isEmpty()) {
|
||||
sb.append('}');
|
||||
} else {
|
||||
sb.append(',').append(JsonFactory.root().getConvert().convertTo(attributes).substring(1));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getAlertTitle() {
|
||||
return alertTitle;
|
||||
}
|
||||
|
||||
public void setAlertTitle(String alertTitle) {
|
||||
this.alertTitle = alertTitle;
|
||||
}
|
||||
|
||||
public String getAlertBody() {
|
||||
return alertBody;
|
||||
}
|
||||
|
||||
public void setAlertBody(String alertBody) {
|
||||
this.alertBody = alertBody;
|
||||
}
|
||||
|
||||
public String getAlertTitleLocKey() {
|
||||
return alertTitleLocKey;
|
||||
}
|
||||
|
||||
public void setAlertTitleLocKey(String alertTitleLocKey) {
|
||||
this.alertTitleLocKey = alertTitleLocKey;
|
||||
}
|
||||
|
||||
public String[] getAlertTitleLocArgs() {
|
||||
return alertTitleLocArgs;
|
||||
}
|
||||
|
||||
public void setAlertTitleLocArgs(String[] alertTitleLocArgs) {
|
||||
this.alertTitleLocArgs = alertTitleLocArgs;
|
||||
}
|
||||
|
||||
public String getAlertActionLocKey() {
|
||||
return alertActionLocKey;
|
||||
}
|
||||
|
||||
public void setAlertActionLocKey(String alertActionLocKey) {
|
||||
this.alertActionLocKey = alertActionLocKey;
|
||||
}
|
||||
|
||||
public String getAlertLocKey() {
|
||||
return alertLocKey;
|
||||
}
|
||||
|
||||
public void setAlertLocKey(String alertLocKey) {
|
||||
this.alertLocKey = alertLocKey;
|
||||
}
|
||||
|
||||
public String[] getAlertLocArgs() {
|
||||
return alertLocArgs;
|
||||
}
|
||||
|
||||
public void setAlertLocArgs(String[] alertLocArgs) {
|
||||
this.alertLocArgs = alertLocArgs;
|
||||
}
|
||||
|
||||
public String getAlertLaunchImage() {
|
||||
return alertLaunchImage;
|
||||
}
|
||||
|
||||
public void setAlertLaunchImage(String alertLaunchImage) {
|
||||
this.alertLaunchImage = alertLaunchImage;
|
||||
}
|
||||
|
||||
public int getContentAvailable() {
|
||||
return contentAvailable;
|
||||
}
|
||||
|
||||
public void setContentAvailable(int contentAvailable) {
|
||||
this.contentAvailable = contentAvailable;
|
||||
}
|
||||
|
||||
public String getAlert() {
|
||||
return alert;
|
||||
}
|
||||
|
||||
public void setAlert(String alert) {
|
||||
this.alert = alert;
|
||||
}
|
||||
|
||||
public int getBadge() {
|
||||
return badge;
|
||||
}
|
||||
|
||||
public void setBadge(int badge) {
|
||||
this.badge = badge;
|
||||
}
|
||||
|
||||
public String getSound() {
|
||||
return sound;
|
||||
}
|
||||
|
||||
public void setSound(String sound) {
|
||||
this.sound = sound;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.service.apns;
|
||||
|
||||
import com.wentch.redkale.convert.json.*;
|
||||
import com.wentch.redkale.service.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.*;
|
||||
import java.security.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.*;
|
||||
import javax.net.ssl.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@AutoLoad(false)
|
||||
@LocalService
|
||||
public class ApnsService implements Service {
|
||||
|
||||
private static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
|
||||
@Resource
|
||||
protected JsonConvert convert;
|
||||
|
||||
@Resource(name = "property.apns.certpwd")
|
||||
protected String apnscertpwd = "1"; //证书的密码
|
||||
|
||||
@Resource(name = "property.apns.certpath") //用来加载证书用
|
||||
protected String apnscertpath = "apnspushdev_cert.p12";
|
||||
|
||||
@Resource(name = "property.apns.pushaddr") //
|
||||
protected String apnspushaddr = "gateway.sandbox.push.apple.com";
|
||||
|
||||
@Resource(name = "property.apns.pushport") //
|
||||
protected int apnspushport = 2195;
|
||||
|
||||
@Resource(name = "property.apns.buffersize") //
|
||||
protected int apnsbuffersize = 4096;
|
||||
|
||||
private boolean inited = false;
|
||||
|
||||
private final CountDownLatch cdl = new CountDownLatch(1);
|
||||
|
||||
private SSLSocketFactory sslFactory;
|
||||
|
||||
@Override
|
||||
public void init(AnyValue conf) {
|
||||
new Thread() {
|
||||
{
|
||||
setDaemon(true);
|
||||
setPriority(Thread.MAX_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final String path = "/" + this.getClass().getPackage().getName().replace('.', '/') + "/" + apnscertpath;
|
||||
KeyStore ks = KeyStore.getInstance("PKCS12");
|
||||
InputStream in = ApnsService.class.getResourceAsStream(path);
|
||||
KeyManagerFactory kf = null;
|
||||
if (in != null) {
|
||||
ks.load(in, apnscertpwd.toCharArray());
|
||||
in.close();
|
||||
kf = KeyManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
kf.init(ks, apnscertpwd.toCharArray());
|
||||
}
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init((KeyStore) null);
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(kf == null ? new KeyManager[0] : kf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||
ApnsService.this.sslFactory = context.getSocketFactory();
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, this.getClass().getSimpleName() + " init SSLContext error", e);
|
||||
} finally {
|
||||
inited = true;
|
||||
cdl.countDown();
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(AnyValue conf) {
|
||||
}
|
||||
|
||||
private Socket getPushSocket() throws IOException {
|
||||
if (!this.inited) {
|
||||
try {
|
||||
cdl.await();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
Socket pushSocket = sslFactory.createSocket(apnspushaddr, apnspushport);
|
||||
pushSocket.setTcpNoDelay(true);
|
||||
return pushSocket;
|
||||
}
|
||||
|
||||
public void pushApnsMessage(ApnsMessage message) throws IOException {
|
||||
final byte[] tokens = Utility.hexToBin(message.getToken().replaceAll("\\s+", ""));
|
||||
ByteBuffer buffer = ByteBuffer.allocate(apnsbuffersize);
|
||||
buffer.put((byte) 2); //固定命令号
|
||||
buffer.putInt(0); //下面数据的长度
|
||||
|
||||
buffer.put((byte) 1); //token
|
||||
buffer.putShort((short) tokens.length);
|
||||
buffer.put(tokens);
|
||||
|
||||
buffer.put((byte) 2); //payload
|
||||
final byte[] payload = message.getPayload().toString().getBytes(UTF8);
|
||||
buffer.putShort((short) payload.length);
|
||||
buffer.put(payload);
|
||||
|
||||
if (message.getIdentifier() > 0) {
|
||||
buffer.put((byte) 3); //Notification identifier
|
||||
buffer.putShort((short) 4);
|
||||
buffer.putInt(message.getIdentifier());
|
||||
}
|
||||
if (message.getExpiredate() > 0) {
|
||||
buffer.put((byte) 4); //Expiration date
|
||||
buffer.putShort((short) 4);
|
||||
buffer.putInt(message.getExpiredate());
|
||||
}
|
||||
buffer.put((byte) 5); //Priority
|
||||
buffer.putShort((short) 1);
|
||||
buffer.put((byte) message.getPriority());
|
||||
|
||||
final int pos = buffer.position();
|
||||
buffer.position(1);
|
||||
buffer.putInt(pos - 5);
|
||||
buffer.position(pos);
|
||||
buffer.flip();
|
||||
|
||||
Socket socket = getPushSocket();
|
||||
socket.getOutputStream().write(buffer.array(), 0, buffer.remaining());
|
||||
socket.close();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ApnsService service = new ApnsService();
|
||||
service.convert = JsonFactory.root().getConvert();
|
||||
service.init(null);
|
||||
|
||||
final String token = "01727b19 b9f8abf4 0891e31d 3446479d a43902e1 819edc44 a073d951 b8b7db90";
|
||||
ApnsPayload payload = new ApnsPayload("您有新的消息", "这是消息内容", 1);
|
||||
System.out.println(payload);
|
||||
service.pushApnsMessage(new ApnsMessage(token, payload));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.service.weixin;
|
||||
|
||||
import com.wentch.redkale.convert.json.*;
|
||||
import static com.wentch.redkale.convert.json.JsonConvert.TYPE_MAP_STRING_STRING;
|
||||
import com.wentch.redkale.service.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import static com.wentch.redkale.util.Utility.getHttpContent;
|
||||
import java.io.*;
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.*;
|
||||
|
||||
/**
|
||||
* 微信服务号Service
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@AutoLoad(false)
|
||||
@LocalService
|
||||
public class WeiXinMPService implements Service {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
|
||||
private final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
|
||||
private final boolean finer = logger.isLoggable(Level.FINER);
|
||||
|
||||
protected final Map<String, String> mpsecrets = new HashMap<>();
|
||||
|
||||
@Resource
|
||||
protected JsonConvert convert;
|
||||
|
||||
// http://m.xxx.com/pipes/wx/verifymp
|
||||
@Resource(name = "property.wxmp.token")
|
||||
protected String mptoken = "";
|
||||
|
||||
@Resource(name = "property.wxmp.corpid")
|
||||
protected String mpcorpid = "wxYYYYYYYYYYYYYY";
|
||||
|
||||
@Resource(name = "property.wxmp.aeskey")
|
||||
protected String mpaeskey = "";
|
||||
|
||||
public WeiXinMPService() {
|
||||
// mpsecrets.put("wxYYYYYYYYYYYYYYYYYY", "xxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
}
|
||||
|
||||
//-----------------------------------微信服务号接口----------------------------------------------------------
|
||||
public RetResult<String> getMPWxunionidByCode(String appid, String code) {
|
||||
try {
|
||||
Map<String, String> wxmap = getMPUserTokenByCode(appid, code);
|
||||
final String unionid = wxmap.get("unionid");
|
||||
if (unionid != null && !unionid.isEmpty()) return new RetResult<>(unionid);
|
||||
return new RetResult<>(1011002);
|
||||
} catch (IOException e) {
|
||||
return new RetResult<>(1011001);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getMPUserTokenByCode(String appid, String code) throws IOException {
|
||||
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid + "&secret=" + mpsecrets.get(appid) + "&code=" + code + "&grant_type=authorization_code";
|
||||
String json = getHttpContent(url);
|
||||
if (finest) logger.finest(url + "--->" + json);
|
||||
Map<String, String> jsonmap = convert.convertFrom(TYPE_MAP_STRING_STRING, json);
|
||||
return getMPUserTokenByOpenid(jsonmap.get("access_token"), jsonmap.get("openid"));
|
||||
}
|
||||
|
||||
public Map<String, String> getMPUserTokenByOpenid(String access_token, String openid) throws IOException {
|
||||
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + access_token + "&openid=" + openid;
|
||||
String json = getHttpContent(url);
|
||||
if (finest) logger.finest(url + "--->" + json);
|
||||
Map<String, String> jsonmap = convert.convertFrom(TYPE_MAP_STRING_STRING, json.replaceFirst("\\[.*\\]", "null"));
|
||||
return jsonmap;
|
||||
}
|
||||
|
||||
public String verifyMPURL(String msgSignature, String timeStamp, String nonce, String echoStr) {
|
||||
String signature = sha1(mptoken, timeStamp, nonce);
|
||||
if (!signature.equals(msgSignature)) throw new RuntimeException("signature verification error");
|
||||
return echoStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用SHA1算法生成安全签名
|
||||
* <p>
|
||||
* @param strings
|
||||
* @return 安全签名
|
||||
*/
|
||||
protected static String sha1(String... strings) {
|
||||
try {
|
||||
Arrays.sort(strings);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
for (String s : strings) md.update(s.getBytes());
|
||||
return Utility.binToHexString(md.digest());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("SHA encryption to generate signature failure", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.service.weixin;
|
||||
|
||||
import com.wentch.redkale.service.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class WeiXinPayResult extends RetResult<String> {
|
||||
|
||||
//待支付
|
||||
public static final short PAYSTATUS_UNPAY = 10;
|
||||
|
||||
//已支付
|
||||
public static final short PAYSTATUS_PAYOK = 30;
|
||||
|
||||
private long orderid;
|
||||
|
||||
private long payid;
|
||||
|
||||
private long payedmoney;
|
||||
|
||||
private short paystatus;
|
||||
|
||||
public WeiXinPayResult() {
|
||||
}
|
||||
|
||||
public WeiXinPayResult(int retcode) {
|
||||
super(retcode);
|
||||
}
|
||||
|
||||
public WeiXinPayResult(long orderid, long payid, short paystatus, long payedmoney, String resultcontent) {
|
||||
this.orderid = orderid;
|
||||
this.payid = payid;
|
||||
this.paystatus = paystatus;
|
||||
this.payedmoney = payedmoney;
|
||||
this.setResult(resultcontent);
|
||||
}
|
||||
|
||||
public long getOrderid() {
|
||||
return orderid;
|
||||
}
|
||||
|
||||
public void setOrderid(long orderid) {
|
||||
this.orderid = orderid;
|
||||
}
|
||||
|
||||
public long getPayid() {
|
||||
return payid;
|
||||
}
|
||||
|
||||
public void setPayid(long payid) {
|
||||
this.payid = payid;
|
||||
}
|
||||
|
||||
public long getPayedmoney() {
|
||||
return payedmoney;
|
||||
}
|
||||
|
||||
public void setPayedmoney(long payedmoney) {
|
||||
this.payedmoney = payedmoney;
|
||||
}
|
||||
|
||||
public short getPaystatus() {
|
||||
return paystatus;
|
||||
}
|
||||
|
||||
public void setPaystatus(short paystatus) {
|
||||
this.paystatus = paystatus;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.service.weixin;
|
||||
|
||||
import com.wentch.redkale.convert.json.*;
|
||||
import com.wentch.redkale.service.*;
|
||||
import static com.wentch.redkale.service.weixin.WeiXinPayResult.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.security.*;
|
||||
import java.text.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.*;
|
||||
import java.util.regex.*;
|
||||
import javax.annotation.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@AutoLoad(false)
|
||||
@LocalService
|
||||
public class WeiXinPayService implements Service {
|
||||
|
||||
private static final DateFormat FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||
|
||||
private static final Pattern PAYXML = Pattern.compile("<([^/>]+)>(.+)</.+>"); // "<([^/>]+)><!\\[CDATA\\[(.+)\\]\\]></.+>"
|
||||
|
||||
public static final int PAY_WX_ERROR = 4012101;//微信支付失败
|
||||
|
||||
public static final int PAY_FALSIFY_ORDER = 4012017;//交易签名被篡改
|
||||
|
||||
public static final int PAY_STATUS_ERROR = 4012018;//订单或者支付状态不正确
|
||||
|
||||
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
|
||||
protected final boolean fine = logger.isLoggable(Level.FINE);
|
||||
|
||||
protected final boolean finer = logger.isLoggable(Level.FINER);
|
||||
|
||||
protected final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
|
||||
@Resource(name = "property.wxpay.appid") //公众账号ID
|
||||
protected String wxpayappid = "wxYYYYYYYYYYYY";
|
||||
|
||||
@Resource(name = "property.wxpay.mchid") //商户ID
|
||||
protected String wxpaymchid = "xxxxxxxxxxx";
|
||||
|
||||
@Resource(name = "property.wxpay.sdbmchid") //子商户ID,受理模式必填
|
||||
protected String wxpaysdbmchid = "";
|
||||
|
||||
@Resource(name = "property.wxpay.key") //签名算法需要用到的秘钥
|
||||
protected String wxpaykey = "##########################";
|
||||
|
||||
@Resource(name = "property.wxpay.certpwd")
|
||||
protected String wxpaycertpwd = "xxxxxxxxxx"; //HTTP证书的密码,默认等于MCHID
|
||||
|
||||
@Resource(name = "property.wxpay.certpath") //HTTP证书在服务器中的路径,用来加载证书用
|
||||
protected String wxpaycertpath = "apiclient_cert.p12";
|
||||
|
||||
@Resource
|
||||
protected JsonConvert convert;
|
||||
|
||||
/**
|
||||
* <xml><return_code><![CDATA[SUCCESS]]></return_code>
|
||||
* + "<return_msg><![CDATA[OK]]></return_msg>
|
||||
* + "<appid><![CDATA[wx4ad12c89818dd981]]></appid>
|
||||
* + "<mch_id><![CDATA[1241384602]]></mch_id>
|
||||
* + "<nonce_str><![CDATA[RpGucJ6wKtPgpTJy]]></nonce_str>
|
||||
* + "<sign><![CDATA[DFD99D5DA7DCA4FB5FB79ECAD49B9369]]></sign>
|
||||
* + "<result_code><![CDATA[SUCCESS]]></result_code>
|
||||
* + "<prepay_id><![CDATA[wx2015051518135700aaea6bc30284682518]]></prepay_id>
|
||||
* + "<trade_type><![CDATA[JSAPI]]></trade_type>
|
||||
* + "</xml>
|
||||
* <p>
|
||||
* @param orderid
|
||||
* @param payid
|
||||
* @param orderpayid
|
||||
* @param paymoney
|
||||
* @param clientAddr
|
||||
* @param notifyurl
|
||||
* @param map
|
||||
* @return
|
||||
*/
|
||||
public RetResult<Map<String, String>> paying(long orderid, long payid, long orderpayid, long paymoney, String clientAddr, String notifyurl, Map<String, String> map) {
|
||||
RetResult result = null;
|
||||
try {
|
||||
if (!(map instanceof SortedMap)) map = new TreeMap<>(map);
|
||||
map.put("appid", wxpayappid);
|
||||
map.put("mch_id", wxpaymchid);
|
||||
map.put("nonce_str", Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()));
|
||||
map.putIfAbsent("body", "服务");
|
||||
map.put("attach", "" + payid);
|
||||
map.put("out_trade_no", "" + orderpayid);
|
||||
map.put("total_fee", "" + paymoney);
|
||||
map.put("spbill_create_ip", clientAddr);
|
||||
synchronized (FORMAT) {
|
||||
map.put("time_expire", FORMAT.format(new Date(System.currentTimeMillis() + 10 * 60 * 60 * 1000)));
|
||||
}
|
||||
map.put("notify_url", notifyurl);
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
map.forEach((x, y) -> sb.append(x).append('=').append(y).append('&'));
|
||||
sb.append("key=").append(wxpaykey);
|
||||
map.put("sign", Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb.toString().getBytes())).toUpperCase());
|
||||
}
|
||||
if (finest) logger.finest("weixinpaying2: " + orderid + " -> unifiedorder.map =" + map);
|
||||
Map<String, String> wxresult = formatXMLToMap(Utility.postHttpContent("https://api.mch.weixin.qq.com/pay/unifiedorder", formatMapToXML(map)));
|
||||
if (finest) logger.finest("weixinpaying3: " + orderid + " -> unifiedorder.callback =" + wxresult);
|
||||
if (!"SUCCESS".equals(wxresult.get("return_code"))) return new RetResult<>(PAY_WX_ERROR);
|
||||
if (!checkSign(wxresult)) return new RetResult(PAY_FALSIFY_ORDER);
|
||||
/**
|
||||
* "appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入 "timeStamp":" 1395712654", //时间戳,自1970年以来的秒数 "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串 "package" :
|
||||
* "prepay_id=u802345jgfjsdfgsdg888", "signType" : "MD5", //微信签名方式: "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
|
||||
*/
|
||||
Map<String, String> rs = new TreeMap<>();
|
||||
rs.put("appId", this.wxpayappid);
|
||||
rs.put("timeStamp", Long.toString(System.currentTimeMillis() / 1000));
|
||||
rs.put("nonceStr", Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()));
|
||||
rs.put("package", "prepay_id=" + wxresult.get("prepay_id"));
|
||||
rs.put("signType", "MD5");
|
||||
{
|
||||
final StringBuilder sb2 = new StringBuilder();
|
||||
rs.forEach((x, y) -> sb2.append(x).append('=').append(y).append('&'));
|
||||
sb2.append("key=").append(wxpaykey);
|
||||
rs.put("paySign", Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb2.toString().getBytes())).toUpperCase());
|
||||
}
|
||||
if (finest) logger.finest("weixinpaying4: " + orderid + " -> unifiedorder.result =" + rs);
|
||||
RetResult rr = new RetResult(rs);
|
||||
rr.setRetinfo("" + orderpayid);
|
||||
return rr;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "paying error.", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public RetResult closepay(long orderpayid) {
|
||||
RetResult result = null;
|
||||
try {
|
||||
Map<String, String> map = new TreeMap<>();
|
||||
map.put("appid", wxpayappid);
|
||||
map.put("mch_id", wxpaymchid);
|
||||
map.put("nonce_str", Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()));
|
||||
map.put("out_trade_no", "" + orderpayid);
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
map.forEach((x, y) -> sb.append(x).append('=').append(y).append('&'));
|
||||
sb.append("key=").append(wxpaykey);
|
||||
map.put("sign", Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb.toString().getBytes())).toUpperCase());
|
||||
}
|
||||
if (finest) logger.finest("weixinclosepay2: " + orderpayid + " -> closeorder.map =" + map);
|
||||
Map<String, String> wxresult = formatXMLToMap(Utility.postHttpContent("https://api.mch.weixin.qq.com/pay/closeorder", formatMapToXML(map)));
|
||||
if (finest) logger.finest("weixinclosepay3: " + orderpayid + " -> closeorder.callback =" + wxresult);
|
||||
if (!"SUCCESS".equals(wxresult.get("return_code"))) return new RetResult<>(PAY_WX_ERROR);
|
||||
if (!checkSign(wxresult)) return new RetResult(PAY_FALSIFY_ORDER);
|
||||
return new RetResult(wxresult);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "closepay error: " + orderpayid, e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public WeiXinPayResult checkPay(long orderid, long orderpayid) {
|
||||
WeiXinPayResult result = new WeiXinPayResult(PAY_STATUS_ERROR);
|
||||
try {
|
||||
Map<String, String> map = new TreeMap<>();
|
||||
map.put("appid", wxpayappid);
|
||||
map.put("mch_id", wxpaymchid);
|
||||
map.put("out_trade_no", "" + orderpayid);
|
||||
map.put("nonce_str", Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()));
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
map.forEach((x, y) -> sb.append(x).append('=').append(y).append('&'));
|
||||
sb.append("key=").append(wxpaykey);
|
||||
map.put("sign", Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb.toString().getBytes())).toUpperCase());
|
||||
}
|
||||
Map<String, String> wxresult = formatXMLToMap(Utility.postHttpContent("https://api.mch.weixin.qq.com/pay/orderquery", formatMapToXML(map)));
|
||||
return callbackPay(wxresult);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.FINER, "check weixinpay[" + orderid + "] except", e);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <xml>
|
||||
* <appid><![CDATA[wx4ad12c89818dd981]]></appid>
|
||||
* <attach><![CDATA[10000070334]]></attach>
|
||||
* <bank_type><![CDATA[ICBC_DEBIT]]></bank_type>
|
||||
* <cash_fee><![CDATA[10]]></cash_fee>
|
||||
* <fee_type><![CDATA[CNY]]></fee_type>
|
||||
* <is_subscribe><![CDATA[Y]]></is_subscribe>
|
||||
* <mch_id><![CDATA[1241384602]]></mch_id>
|
||||
* <nonce_str><![CDATA[14d69ac6d6525f27dc9bcbebc]]></nonce_str>
|
||||
* <openid><![CDATA[ojEVbsyDUzGqlgX3eDgmAMaUDucA]]></openid>
|
||||
* <out_trade_no><![CDATA[1000072334]]></out_trade_no>
|
||||
* <result_code><![CDATA[SUCCESS]]></result_code>
|
||||
* <return_code><![CDATA[SUCCESS]]></return_code>
|
||||
* <sign><![CDATA[60D95E25EA9C4F54BD1020952303C4E2]]></sign>
|
||||
* <time_end><![CDATA[20150519085546]]></time_end>
|
||||
* <total_fee>10</total_fee>
|
||||
* <trade_type><![CDATA[JSAPI]]></trade_type>
|
||||
* <transaction_id><![CDATA[1009630061201505190139511926]]></transaction_id>
|
||||
* </xml>
|
||||
* <p>
|
||||
* @param map
|
||||
* @return
|
||||
*/
|
||||
public WeiXinPayResult callbackPay(Map<String, String> map) {
|
||||
if (!"SUCCESS".equals(map.get("return_code"))) return new WeiXinPayResult(PAY_WX_ERROR);
|
||||
if (!(map instanceof SortedMap)) map = new TreeMap<>(map);
|
||||
if (!checkSign(map)) return new WeiXinPayResult(PAY_FALSIFY_ORDER);
|
||||
String state = map.get("trade_state");
|
||||
if (state == null && "SUCCESS".equals(map.get("result_code")) && Long.parseLong(map.get("total_fee")) > 0) {
|
||||
state = "SUCCESS";
|
||||
}
|
||||
short paystatus = "SUCCESS".equals(state) ? PAYSTATUS_PAYOK : PAYSTATUS_UNPAY;
|
||||
return new WeiXinPayResult(Long.parseLong(map.get("out_trade_no")), Long.parseLong(map.get("attach")), paystatus, Long.parseLong(map.get("total_fee")), convert.convertTo(map));
|
||||
}
|
||||
|
||||
protected static String formatMapToXML(final Map<String, String> map) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("<xml>");
|
||||
map.forEach((x, y) -> sb.append('<').append(x).append('>').append(y.replace("<", "<").replace(">", ">").replace("&", "&")).append("</").append(x).append('>'));
|
||||
sb.append("</xml>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected boolean checkSign(Map<String, String> map) {
|
||||
if (!(map instanceof SortedMap)) map = new TreeMap<>(map);
|
||||
String sign = map.remove("sign");
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
map.forEach((x, y) -> sb.append(x).append('=').append(y).append('&'));
|
||||
sb.append("key=").append(wxpaykey);
|
||||
try {
|
||||
return sign.equals(Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb.toString().getBytes())).toUpperCase());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> formatXMLToMap(final String xml) {
|
||||
Map<String, String> map = new TreeMap<>();
|
||||
Matcher m = PAYXML.matcher(xml.substring(xml.indexOf('>') + 1));
|
||||
while (m.find()) {
|
||||
String val = m.group(2);
|
||||
if (val.startsWith("<![CDATA[")) val = val.substring("<![CDATA[".length(), val.length() - 3);
|
||||
map.put(m.group(1), val);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.service.weixin;
|
||||
|
||||
import com.wentch.redkale.convert.json.*;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
|
||||
/**
|
||||
* 微信企业号Service
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class WeiXinQYMessage {
|
||||
|
||||
private String agentid = "1";
|
||||
|
||||
private String msgtype = "text";
|
||||
|
||||
private Map<String, String> text;
|
||||
|
||||
private String touser = "@all";
|
||||
|
||||
private String toparty;
|
||||
|
||||
private String totag;
|
||||
|
||||
private String safe;
|
||||
|
||||
private Supplier<String> contentSupplier;
|
||||
|
||||
public WeiXinQYMessage() {
|
||||
}
|
||||
|
||||
public WeiXinQYMessage(String agentid, String text) {
|
||||
this.agentid = agentid;
|
||||
setTextMessage(text);
|
||||
}
|
||||
|
||||
public WeiXinQYMessage(String agentid, Supplier<String> contentSupplier) {
|
||||
this.agentid = agentid;
|
||||
this.contentSupplier = contentSupplier;
|
||||
}
|
||||
|
||||
public final void setTextMessage(String content) {
|
||||
if (text == null) text = new HashMap<>();
|
||||
text.put("content", content);
|
||||
}
|
||||
|
||||
public void supplyContent() {
|
||||
if (contentSupplier != null) setTextMessage(contentSupplier.get());
|
||||
}
|
||||
|
||||
public String getAgentid() {
|
||||
return agentid;
|
||||
}
|
||||
|
||||
public void setAgentid(String agentid) {
|
||||
this.agentid = agentid;
|
||||
}
|
||||
|
||||
public String getMsgtype() {
|
||||
return msgtype;
|
||||
}
|
||||
|
||||
public void setMsgtype(String msgtype) {
|
||||
this.msgtype = msgtype;
|
||||
}
|
||||
|
||||
public Map<String, String> getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(Map<String, String> text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getTouser() {
|
||||
return touser;
|
||||
}
|
||||
|
||||
public void setTouser(String touser) {
|
||||
this.touser = touser;
|
||||
}
|
||||
|
||||
public String getToparty() {
|
||||
return toparty;
|
||||
}
|
||||
|
||||
public void setToparty(String toparty) {
|
||||
this.toparty = toparty;
|
||||
}
|
||||
|
||||
public String getTotag() {
|
||||
return totag;
|
||||
}
|
||||
|
||||
public void setTotag(String totag) {
|
||||
this.totag = totag;
|
||||
}
|
||||
|
||||
public String getSafe() {
|
||||
return safe;
|
||||
}
|
||||
|
||||
public void setSafe(String safe) {
|
||||
this.safe = safe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JsonFactory.root().getConvert().convertTo(this);
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.service.weixin;
|
||||
|
||||
import com.wentch.redkale.boot.*;
|
||||
import com.wentch.redkale.convert.json.*;
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.service.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import static com.wentch.redkale.util.Utility.*;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.charset.*;
|
||||
import java.security.*;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.logging.*;
|
||||
import javax.annotation.*;
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@AutoLoad(false)
|
||||
@LocalService
|
||||
public class WeiXinQYService implements Service {
|
||||
|
||||
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||
|
||||
private final boolean finest = logger.isLoggable(Level.FINEST);
|
||||
|
||||
private final boolean finer = logger.isLoggable(Level.FINER);
|
||||
|
||||
private static class Token {
|
||||
|
||||
public String token;
|
||||
|
||||
public long expires = 7100000;
|
||||
|
||||
public long accesstime;
|
||||
}
|
||||
|
||||
private static final String BASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
private static final Charset CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
protected static final Type MAPTYPE = new TypeToken<Map<String, String>>() {
|
||||
}.getType();
|
||||
|
||||
@Resource
|
||||
protected JsonConvert convert;
|
||||
|
||||
//------------------------------------------------------------------------------------------------------
|
||||
// http://oa.xxxx.com/pipes/wx/verifyqy
|
||||
@Resource(name = "property.wxqy.token")
|
||||
protected String qytoken = "";
|
||||
|
||||
@Resource(name = "property.wxqy.corpid")
|
||||
protected String qycorpid = "wxYYYYYYYYYYYYYYYY";
|
||||
|
||||
@Resource(name = "property.wxqy.aeskey")
|
||||
protected String qyaeskey = "";
|
||||
|
||||
@Resource(name = "property.wxqy.secret")
|
||||
private String qysecret = "#########################";
|
||||
|
||||
private SecretKeySpec qykeyspec;
|
||||
|
||||
private IvParameterSpec qyivspec;
|
||||
|
||||
private final Token qyAccessToken = new Token();
|
||||
|
||||
//------------------------------------------------------------------------------------------------------
|
||||
public WeiXinQYService() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
WeiXinQYService service = Application.singleton(WeiXinQYService.class);
|
||||
|
||||
WeiXinQYMessage message = new WeiXinQYMessage();
|
||||
message.setTextMessage("【测试】duang!");
|
||||
message.setAgentid("2");
|
||||
service.sendQYMessage(message);
|
||||
}
|
||||
|
||||
//-----------------------------------微信企业号接口----------------------------------------------------------
|
||||
public Map<String, String> getQYUserCode(String code, String agentid) throws IOException {
|
||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=" + getQYAccessToken() + "&code=" + code + "&agentid=" + agentid;
|
||||
String json = getHttpContent(url);
|
||||
if (finest) logger.finest(url + "--->" + json);
|
||||
return convert.convertFrom(MAPTYPE, json);
|
||||
}
|
||||
|
||||
public void sendQYTextMessage(String agentid, String message) {
|
||||
sendQYMessage(new WeiXinQYMessage(agentid, message));
|
||||
}
|
||||
|
||||
public void sendQYTextMessage(String agentid, Supplier<String> contentSupplier) {
|
||||
sendQYMessage(new WeiXinQYMessage(agentid, contentSupplier));
|
||||
}
|
||||
|
||||
public void sendQYMessage(WeiXinQYMessage message) {
|
||||
submit(() -> {
|
||||
String result = null;
|
||||
try {
|
||||
message.supplyContent();
|
||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + getQYAccessToken();
|
||||
result = postHttpContent(url, convert.convertTo(message));
|
||||
if (finest) logger.finest("sendQYMessage ok: " + message + " -> " + result);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "sendQYMessage error: " + message + " -> " + result, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String verifyQYURL(String msgSignature, String timeStamp, String nonce, String echoStr) {
|
||||
String signature = sha1(qytoken, timeStamp, nonce, echoStr);
|
||||
if (!signature.equals(msgSignature)) throw new RuntimeException("signature verification error");
|
||||
return decryptQY(echoStr);
|
||||
}
|
||||
|
||||
protected String getQYAccessToken() throws IOException {
|
||||
if (qyAccessToken.accesstime < System.currentTimeMillis() - qyAccessToken.expires) qyAccessToken.token = null;
|
||||
if (qyAccessToken.token == null) {
|
||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + qycorpid + "&corpsecret=" + qysecret;
|
||||
String json = getHttpContent(url);
|
||||
if (finest) logger.finest(url + "--->" + json);
|
||||
Map<String, String> jsonmap = convert.convertFrom(MAPTYPE, json);
|
||||
qyAccessToken.accesstime = System.currentTimeMillis();
|
||||
qyAccessToken.token = jsonmap.get("access_token");
|
||||
String exp = jsonmap.get("expires_in");
|
||||
if (exp != null) qyAccessToken.expires = (Integer.parseInt(exp) - 100) * 1000;
|
||||
}
|
||||
return qyAccessToken.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将公众平台回复用户的消息加密打包.
|
||||
* <ol>
|
||||
* <li>对要发送的消息进行AES-CBC加密</li>
|
||||
* <li>生成安全签名</li>
|
||||
* <li>将消息密文和安全签名打包成xml格式</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* @param replyMsg 公众平台待回复用户的消息,xml格式的字符串
|
||||
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
|
||||
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
|
||||
* <p>
|
||||
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
|
||||
*/
|
||||
protected String encryptQYMessage(String replyMsg, String timeStamp, String nonce) {
|
||||
// 加密
|
||||
String encrypt = encryptQY(random16String(), replyMsg);
|
||||
|
||||
// 生成安全签名
|
||||
if (timeStamp == null || timeStamp.isEmpty()) timeStamp = Long.toString(System.currentTimeMillis());
|
||||
String signature = sha1(qytoken, timeStamp, nonce, encrypt);
|
||||
|
||||
// System.out.println("发送给平台的签名是: " + signature[1].toString());
|
||||
// 生成发送的xml
|
||||
return "<xml>\n<Encrypt><![CDATA[" + encrypt + "]]></Encrypt>\n"
|
||||
+ "<MsgSignature><![CDATA[" + signature + "]]></MsgSignature>\n"
|
||||
+ "<TimeStamp>" + timeStamp + "</TimeStamp>\n"
|
||||
+ "<Nonce><![CDATA[" + nonce + "]]></Nonce>\n</xml>";
|
||||
}
|
||||
|
||||
protected String decryptQYMessage(String msgSignature, String timeStamp, String nonce, String postData) {
|
||||
// 密钥,公众账号的app secret
|
||||
// 提取密文
|
||||
String encrypt = postData.substring(postData.indexOf("<Encrypt><![CDATA[") + "<Encrypt><![CDATA[".length(), postData.indexOf("]]></Encrypt>"));
|
||||
// 验证安全签名
|
||||
if (!sha1(qytoken, timeStamp, nonce, encrypt).equals(msgSignature)) throw new RuntimeException("signature verification error");
|
||||
return decryptQY(encrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对明文进行加密.
|
||||
* <p>
|
||||
* @param randomStr
|
||||
* @param text 需要加密的明文
|
||||
* @return 加密后base64编码的字符串
|
||||
*/
|
||||
protected String encryptQY(String randomStr, String text) {
|
||||
ByteArray bytes = new ByteArray();
|
||||
byte[] randomStrBytes = randomStr.getBytes(CHARSET);
|
||||
byte[] textBytes = text.getBytes(CHARSET);
|
||||
byte[] corpidBytes = qycorpid.getBytes(CHARSET);
|
||||
|
||||
// randomStr + networkBytesOrder + text + qycorpid
|
||||
bytes.add(randomStrBytes);
|
||||
bytes.addInt(textBytes.length);
|
||||
bytes.add(textBytes);
|
||||
bytes.add(corpidBytes);
|
||||
|
||||
// ... + pad: 使用自定义的填充方式对明文进行补位填充
|
||||
byte[] padBytes = encodePKCS7(bytes.count());
|
||||
bytes.add(padBytes);
|
||||
|
||||
// 获得最终的字节流, 未加密
|
||||
try {
|
||||
// 加密
|
||||
byte[] encrypted = createQYCipher(Cipher.ENCRYPT_MODE).doFinal(bytes.directBytes(), 0, bytes.count());
|
||||
// 使用BASE64对加密后的字符串进行编码
|
||||
return Base64.getEncoder().encodeToString(encrypted);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("AES加密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String decryptQY(String text) {
|
||||
byte[] original;
|
||||
try {
|
||||
// 使用BASE64对密文进行解码
|
||||
original = createQYCipher(Cipher.DECRYPT_MODE).doFinal(Base64.getDecoder().decode(text));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("AES解密失败", e);
|
||||
}
|
||||
try {
|
||||
// 去除补位字符
|
||||
byte[] bytes = decodePKCS7(original);
|
||||
// 分离16位随机字符串,网络字节序和corpid
|
||||
int xmlLength = (bytes[16] & 0xFF) << 24 | (bytes[17] & 0xFF) << 16 | (bytes[18] & 0xFF) << 8 | bytes[19] & 0xFF;
|
||||
if (!qycorpid.equals(new String(bytes, 20 + xmlLength, bytes.length - 20 - xmlLength, CHARSET))) {
|
||||
throw new RuntimeException("corpid校验失败");
|
||||
}
|
||||
return new String(bytes, 20, xmlLength, CHARSET);
|
||||
} catch (RuntimeException e) {
|
||||
if (e.getMessage().contains("corpid")) throw e;
|
||||
throw new RuntimeException("解密后得到的buffer非法", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Cipher createQYCipher(int mode) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); //AES192、256位加密解密 需要将新版 local_policy.jar、US_export_policy.jar两个文件覆盖到 ${JDK_HOME}/jre/lib/security/下
|
||||
if (qykeyspec == null) {
|
||||
byte[] aeskeyBytes = Base64.getDecoder().decode(qyaeskey + "=");
|
||||
qykeyspec = new SecretKeySpec(aeskeyBytes, "AES");
|
||||
qyivspec = new IvParameterSpec(aeskeyBytes, 0, 16);
|
||||
}
|
||||
cipher.init(mode, qykeyspec, qyivspec);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
protected void submit(Runnable runner) {
|
||||
Thread thread = Thread.currentThread();
|
||||
if (thread instanceof WorkThread) {
|
||||
((WorkThread) thread).submit(runner);
|
||||
return;
|
||||
}
|
||||
runner.run();
|
||||
}
|
||||
|
||||
//-----------------------------------通用接口----------------------------------------------------------
|
||||
// 随机生成16位字符串
|
||||
protected static String random16String() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
sb.append(BASE.charAt(RANDOM.nextInt(BASE.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用SHA1算法生成安全签名
|
||||
* <p>
|
||||
* @param strings
|
||||
* @return 安全签名
|
||||
*/
|
||||
protected static String sha1(String... strings) {
|
||||
try {
|
||||
Arrays.sort(strings);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
for (String s : strings) md.update(s.getBytes());
|
||||
return Utility.binToHexString(md.digest());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("SHA encryption to generate signature failure", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得对明文进行补位填充的字节.
|
||||
* <p>
|
||||
* @param count 需要进行填充补位操作的明文字节个数
|
||||
* @return 补齐用的字节数组
|
||||
*/
|
||||
private static byte[] encodePKCS7(int count) {
|
||||
// 计算需要填充的位数
|
||||
int amountToPad = 32 - (count % 32);
|
||||
if (amountToPad == 0) amountToPad = 32;
|
||||
// 获得补位所用的字符
|
||||
char padChr = (char) (byte) (amountToPad & 0xFF);
|
||||
StringBuilder tmp = new StringBuilder();
|
||||
for (int index = 0; index < amountToPad; index++) {
|
||||
tmp.append(padChr);
|
||||
}
|
||||
return tmp.toString().getBytes(CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除解密后明文的补位字符
|
||||
* <p>
|
||||
* @param decrypted 解密后的明文
|
||||
* @return 删除补位字符后的明文
|
||||
*/
|
||||
private static byte[] decodePKCS7(byte[] decrypted) {
|
||||
int pad = (int) decrypted[decrypted.length - 1];
|
||||
if (pad < 1 || pad > 32) pad = 0;
|
||||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user