1.创建基础bean
public final class threadfactory implements java.util.concurrent.threadfactory { private static final atomicinteger poolnumber = new atomicinteger(1); private final threadgroup group; private final atomicinteger threadnumber = new atomicinteger(1); private final string nameprefix; public threadfactory(string groupflag, string functionname) { securitymanager s = system.getsecuritymanager(); this.group = s != null ? s.getthreadgroup() : thread.currentthread().getthreadgroup(); this.nameprefix = this.buildprefix(groupflag, functionname, poolnumber); } private string buildprefix(string vendorflag, string functionname, final atomicinteger poolnumber) { stringbuffer sb = (new stringbuffer("pool-")).append(poolnumber.getandincrement()).append("-thread-"); string connchar = "-"; if (null != vendorflag && !"".equals(vendorflag)) { sb.append(vendorflag).append(connchar); } if (null != functionname && !"".equals(functionname)) { sb.append(functionname).append(connchar); } return sb.tostring(); } public thread newthread(runnable r) { string threadname = this.nameprefix + this.threadnumber.getandincrement(); thread t = new thread(this.group, r, threadname, 0l); if (t.isdaemon()) { t.setdaemon(false); } if (t.getpriority() != 5) { t.setpriority(5); } return t; } }
public class qiweimarkdownmessage<t> { private string msgtype; private t markdown; public qiweimarkdownmessage() { } public string getmsgtype() { return this.msgtype; } public t getmarkdown() { return this.markdown; } public void setmsgtype(final string msgtype) { this.msgtype = msgtype; } public void setmarkdown(final t markdown) { this.markdown = markdown; } public boolean equals(final object o) { if (o == this) { return true; } else if (!(o instanceof qiweimarkdownmessage)) { return false; } else { qiweimarkdownmessage<?> other = (qiweimarkdownmessage)o; if (!other.canequal(this)) { return false; } else { object this$msgtype = this.getmsgtype(); object other$msgtype = other.getmsgtype(); if (this$msgtype == null) { if (other$msgtype != null) { return false; } } else if (!this$msgtype.equals(other$msgtype)) { return false; } object this$markdown = this.getmarkdown(); object other$markdown = other.getmarkdown(); if (this$markdown == null) { if (other$markdown != null) { return false; } } else if (!this$markdown.equals(other$markdown)) { return false; } return true; } } } protected boolean canequal(final object other) { return other instanceof qiweimarkdownmessage; } public int hashcode() { int result = 1; object $msgtype = this.getmsgtype(); result = result * 59 + ($msgtype == null ? 43 : $msgtype.hashcode()); object $markdown = this.getmarkdown(); result = result * 59 + ($markdown == null ? 43 : $markdown.hashcode()); return result; } public string tostring() { return "qiweimarkdownmessage(msgtype=" + this.getmsgtype() + ", markdown=" + this.getmarkdown() + ")"; } }
public class qiweimessagecontext { private string content; private string mentioned_list; private string mentioned_mobile_list; public qiweimessagecontext() { } public string getcontent() { return this.content; } public string getmentioned_list() { return this.mentioned_list; } public string getmentioned_mobile_list() { return this.mentioned_mobile_list; } public void setcontent(final string content) { this.content = content; } public void setmentioned_list(final string mentioned_list) { this.mentioned_list = mentioned_list; } public void setmentioned_mobile_list(final string mentioned_mobile_list) { this.mentioned_mobile_list = mentioned_mobile_list; } public boolean equals(final object o) { if (o == this) { return true; } else if (!(o instanceof qiweimessagecontext)) { return false; } else { qiweimessagecontext other = (qiweimessagecontext)o; if (!other.canequal(this)) { return false; } else { label47: { object this$content = this.getcontent(); object other$content = other.getcontent(); if (this$content == null) { if (other$content == null) { break label47; } } else if (this$content.equals(other$content)) { break label47; } return false; } object this$mentioned_list = this.getmentioned_list(); object other$mentioned_list = other.getmentioned_list(); if (this$mentioned_list == null) { if (other$mentioned_list != null) { return false; } } else if (!this$mentioned_list.equals(other$mentioned_list)) { return false; } object this$mentioned_mobile_list = this.getmentioned_mobile_list(); object other$mentioned_mobile_list = other.getmentioned_mobile_list(); if (this$mentioned_mobile_list == null) { if (other$mentioned_mobile_list != null) { return false; } } else if (!this$mentioned_mobile_list.equals(other$mentioned_mobile_list)) { return false; } return true; } } } protected boolean canequal(final object other) { return other instanceof qiweimessagecontext; } public int hashcode() { int result = 1; object $content = this.getcontent(); result = result * 59 + ($content == null ? 43 : $content.hashcode()); object $mentioned_list = this.getmentioned_list(); result = result * 59 + ($mentioned_list == null ? 43 : $mentioned_list.hashcode()); object $mentioned_mobile_list = this.getmentioned_mobile_list(); result = result * 59 + ($mentioned_mobile_list == null ? 43 : $mentioned_mobile_list.hashcode()); return result; } public string tostring() { return "qiweimessagecontext(content=" + this.getcontent() + ", mentioned_list=" + this.getmentioned_list() + ", mentioned_mobile_list=" + this.getmentioned_mobile_list() + ")"; } }
public class machineutils { public machineutils() { } public static string gethostipfromdockercontainer() { string ip = null; try { enumeration networkinterfaces = networkinterface.getnetworkinterfaces(); while(networkinterfaces.hasmoreelements()) { networkinterface networkinterface = (networkinterface)networkinterfaces.nextelement(); if (!"docker0".equals(networkinterface.getdisplayname()) && !"lo".equals(networkinterface.getdisplayname())) { enumeration<inetaddress> inetaddresses = networkinterface.getinetaddresses(); stringjoiner ipjoiner = new stringjoiner("/", "(", ")"); while(inetaddresses.hasmoreelements()) { inetaddress inetaddress = (inetaddress)inetaddresses.nextelement(); ipjoiner.add(inetaddress.gethostaddress()); } ip = ipjoiner.tostring(); break; } } } catch (exception var6) { system.out.println("获取容器ip信息失败:" + var6); ip = "未获取到容器对应宿主机ip信息"; } return ip; } public static machineutils.machine getmachineinfo() { machineutils.machine machine = new machineutils.machine(); try { inetaddress address = getlocalhostlanaddress(); string ip = address.gethostaddress(); string hostname = address.gethostname(); machine.ip = ip; machine.hostname = hostname; } catch (exception var4) { machine.hostname = "localhost"; machine.ip = "127.0.0.1"; } return machine; } private static inetaddress getlocalhostlanaddress() throws unknownhostexception { try { inetaddress candidateaddress = null; enumeration ifaces = networkinterface.getnetworkinterfaces(); while(ifaces.hasmoreelements()) { networkinterface iface = (networkinterface)ifaces.nextelement(); enumeration inetaddrs = iface.getinetaddresses(); while(inetaddrs.hasmoreelements()) { inetaddress inetaddr = (inetaddress)inetaddrs.nextelement(); if (!inetaddr.isloopbackaddress()) { if (inetaddr.issitelocaladdress()) { return inetaddr; } if (candidateaddress == null) { candidateaddress = inetaddr; } } } } if (candidateaddress != null) { return candidateaddress; } else { inetaddress jdksuppliedaddress = inetaddress.getlocalhost(); if (jdksuppliedaddress == null) { throw new unknownhostexception("the jdk inetaddress.getlocalhost() method unexpectedly returned null."); } else { return jdksuppliedaddress; } } } catch (exception var5) { unknownhostexception unknownhostexception = new unknownhostexception("failed to determine lan address: " + var5); unknownhostexception.initcause(var5); throw unknownhostexception; } } public static class machine { public string ip = ""; public string hostname = ""; public machine() { } } }
public class throwableutils { public throwableutils() { } public static string getthrowablestacktrace(throwable t) { stringwriter sw = new stringwriter(); t.printstacktrace(new printwriter(sw, true)); string result = sw.getbuffer().tostring(); return result.length() > 3700 ? result.substring(0, 3700) + "\n..." : result; } }
public class okhttputil { private static final logger log = loggerfactory.getlogger(okhttputil.class); private static volatile okhttpclient okhttpclient = null; private static volatile semaphore semaphore = null; private static final string content_type = "content-type"; private okhttputil() { if (okhttpclient == null) { class var1 = okhttputil.class; synchronized(okhttputil.class) { if (okhttpclient == null) { trustmanager[] trustmanagers = buildtrustmanagers(); okhttpclient = (new builder()).connecttimeout(15l, timeunit.seconds).writetimeout(20l, timeunit.seconds).readtimeout(20l, timeunit.seconds).sslsocketfactory(createsslsocketfactory(trustmanagers), (x509trustmanager)trustmanagers[0]).hostnameverifier((hostname, session) -> { return true; }).retryonconnectionfailure(true).build(); } } } } public static okhttputil builder() { return new okhttputil(); } private static semaphore getsemaphoreinstance() { class var0 = okhttputil.class; synchronized(okhttputil.class) { if (semaphore == null) { semaphore = new semaphore(0); } } return semaphore; } private static okhttp3.request.builder addheaders(map<string, object> headermap, okhttp3.request.builder builder) { if (headermap != null && !headermap.isempty()) { headermap.foreach((key, value) -> { builder.addheader(key, string.valueof(value)); }); } return builder; } private requestbody setrequestbody(map<string, object> bodyparams, map<string, object> headermap) throws exception { if (headermap != null && headermap.containskey("content-type")) { string contenttype = string.valueof(headermap.get("content-type")); if ("application/x-www-form-urlencoded".equals(contenttype)) { map<string, string> strbodyparammap = new hashmap(); if (bodyparams != null && !bodyparams.isempty()) { bodyparams.foreach((key, value) -> { if (value != null) { strbodyparammap.put(key, (string)value); } }); } return this.buildrequestbodybymap(strbodyparammap); } else { throw new runtimeexception("未知请求类型"); } } else { throw new exception("请求头信息配置中无 content-type配置,请先配置"); } } private requestbody buildrequestbodybymap(map<string, string> bodyparams) { requestbody body = null; okhttp3.formbody.builder formencodingbuilder = new okhttp3.formbody.builder(); if (bodyparams != null) { iterator<string> iterator = bodyparams.keyset().iterator(); string key = ""; while(iterator.hasnext()) { key = (string)iterator.next(); formencodingbuilder.add(key, (string)bodyparams.get(key)); log.info("okclient post表单提交请求参数:{},请求值:{} ", key, bodyparams.get(key)); } } body = formencodingbuilder.build(); return body; } public string get(string url, map<string, object> headermap) { string responsestr = null; try { okhttp3.request.builder builder = (new okhttp3.request.builder()).get().url(url); addheaders(headermap, builder); request request = builder.build(); response response = okhttpclient.newcall(request).execute(); responsestr = response.body().string(); } catch (exception var7) { log.error("httputil 请求出错:{}", var7.getmessage(), var7); } return responsestr; } public string postbyjson(string url, string body, map<string, object> headermap) { string responsestr = null; try { requestbody requestbody = requestbody.create(mediatype.parse("application/json; charset=utf-8"), body); okhttp3.request.builder requestbuilder = (new okhttp3.request.builder()).post(requestbody).url(url); addheaders(headermap, requestbuilder); request request = requestbuilder.build(); call call = okhttpclient.newcall(request); response response = call.execute(); responsestr = response.body().string(); } catch (exception var10) { log.error("httputil 请求出错:{}", var10.getmessage(), var10); } return responsestr; } public string postbyform(string url, map<string, object> bodymap, map<string, object> headermap) { string responsestr = null; try { requestbody body = this.setrequestbody(bodymap, headermap); okhttp3.request.builder requestbuilder = (new okhttp3.request.builder()).post(body).url(url); addheaders(headermap, requestbuilder); request request = requestbuilder.build(); call call = okhttpclient.newcall(request); response response = call.execute(); responsestr = response.body().string(); } catch (exception var10) { log.error("httputil 请求出错:{}", var10.getmessage(), var10); } return responsestr; } public void postjsonasync(string url, string json, final okhttputil.mynetcall mynetcall) throws ioexception { requestbody body = requestbody.create(mediatype.parse("application/json; charset=utf-8"), json); okhttp3.request.builder requestbuilder = new okhttp3.request.builder(); request request = requestbuilder.post(body).url(url).build(); call call = okhttpclient.newcall(request); call.enqueue(new callback() { public void onfailure(call call, ioexception e) { mynetcall.failed(call, e); } public void onresponse(call call, response response) throws ioexception { mynetcall.success(call, response); okhttputil.getsemaphoreinstance().release(); } }); try { getsemaphoreinstance().acquire(); } catch (interruptedexception var9) { var9.printstacktrace(); } } private static sslsocketfactory createsslsocketfactory(trustmanager[] trustallcerts) { sslsocketfactory ssffactory = null; try { sslcontext sc = sslcontext.getinstance("tls"); sc.init((keymanager[])null, trustallcerts, new securerandom()); ssffactory = sc.getsocketfactory(); } catch (exception var3) { var3.printstacktrace(); } return ssffactory; } private static trustmanager[] buildtrustmanagers() { return new trustmanager[]{new x509trustmanager() { public void checkclienttrusted(x509certificate[] chain, string authtype) { } public void checkservertrusted(x509certificate[] chain, string authtype) { } public x509certificate[] getacceptedissuers() { return new x509certificate[0]; } }}; } public interface mynetcall { void success(call call, response response) throws ioexception; void failed(call call, ioexception e); } }
public final class threadpoolfactory { private static final logger logger = loggerfactory.getlogger(threadpoolfactory.class); private static int send_message_coresize = 1; private static int send_message_maxsize = 1; private static int send_message_keepalive = 120; private static int send_message_queuesize = 120; public static executorservice createexecutorservice(int corenum, int maxnum) { return new threadpoolexecutor(corenum, maxnum, (long)send_message_keepalive, timeunit.seconds, new arrayblockingqueue(send_message_queuesize), new threadfactory("robotmessage", "senderrormessage"), new discardoldestpolicy()); } private threadpoolfactory() { } static class monitorrejectedexecutionhandler implements rejectedexecutionhandler { monitorrejectedexecutionhandler() { } public void rejectedexecution(runnable r, threadpoolexecutor executor) { if (!executor.isshutdown()) { try { executor.getqueue().put(r); } catch (interruptedexception var4) { threadpoolfactory.logger.error("when task queue is full, some bad things happened! message is {}", var4); } } } } }
public abstract class abstracesenderrormsgappender extends unsynchronizedappenderbase<iloggingevent> { private string appname; private string messagechannel; private string env; private string sendenabled; public abstracesenderrormsgappender() { } public void append(iloggingevent event) { if (event.getlevel() == level.error && "true".equals(this.sendenabled)) { try { string userlogeerrormessage = event.getformattedmessage(); string stacktraceinfo = ""; ithrowableproxy proxy = event.getthrowableproxy(); if (null != proxy) { throwable t = ((throwableproxy)proxy).getthrowable(); stacktraceinfo = throwableutils.getthrowablestacktrace(t); } string ip = machineutils.gethostipfromdockercontainer(); if (stacktraceinfo.contains("omsexception") || stacktraceinfo.contains("wmsexception") || stacktraceinfo.contains("bmsexception")) { return; } localdatetime datetime = localdatetime.now(); datetimeformatter formatter = datetimeformatter.ofpattern("dd-mm-yyyy hh:mm:ss"); string mestext = "服务名称:<font color=\"warning\">[" + this.appname + "]</font>,发生错误异常\n>ip:[" + ip + "]\n>环境信息:[" + this.env + "]\n>异常信息:[" + userlogeerrormessage + "]\n>time:[" + datetime.format(formatter) + "]\n堆栈信息:[\n" + stacktraceinfo + "]"; this.sendalarmmessage("markdown", mestext); } catch (exception var9) { system.out.println(string.format("日志报警异常,异常原因:{} last=%s", var9.getmessage())); } } } public string getappname() { return this.appname; } public void setappname(string appname) { this.appname = appname; } public string getmessagechannel() { return this.messagechannel; } public void setmessagechannel(string messagechannel) { this.messagechannel = messagechannel; } public string getenv() { return this.env; } public void setenv(string env) { this.env = env; } public string getsendenabled() { return this.sendenabled; } public void setsendenabled(string sendenabled) { this.sendenabled = sendenabled; } protected abstract void sendalarmmessage(string messagetype, string messagecontent); }
public class sendqiweialarmerrormessageappender extends abstracesenderrormsgappender { private static final logger log = loggerfactory.getlogger(sendqiweialarmerrormessageappender.class); private string webhookurl; private alarmservice alarmservice; public sendqiweialarmerrormessageappender() { } protected void sendalarmmessage(string messagetype, string messagecontent) { assert.hastext(this.webhookurl, "机器人webhook地址不能为空"); if (null == this.alarmservice) { synchronized(this) { if (null == this.alarmservice) { this.alarmservice = new wechatalarmserviceimpl(this.webhookurl); } } } this.alarmservice.alarmhandler(messagetype, messagecontent); } public string getwebhookurl() { return this.webhookurl; } public void setwebhookurl(string webhookurl) { this.webhookurl = webhookurl; } }
2.创建配置类
@configurationproperties( prefix = "message.robot" ) public class robotmessagealarmmessageproperties { private string enable; public robotmessagealarmmessageproperties() { } public string getenable() { return this.enable; } public void setenable(final string enable) { this.enable = enable; } public boolean equals(final object o) { if (o == this) { return true; } else if (!(o instanceof robotmessagealarmmessageproperties)) { return false; } else { robotmessagealarmmessageproperties other = (robotmessagealarmmessageproperties)o; if (!other.canequal(this)) { return false; } else { object this$enable = this.getenable(); object other$enable = other.getenable(); if (this$enable == null) { if (other$enable != null) { return false; } } else if (!this$enable.equals(other$enable)) { return false; } return true; } } } protected boolean canequal(final object other) { return other instanceof robotmessagealarmmessageproperties; } public int hashcode() { int result = 1; object $enable = this.getenable(); result = result * 59 + ($enable == null ? 43 : $enable.hashcode()); return result; } public string tostring() { return "robotmessagealarmmessageproperties(enable=" + this.getenable() + ")"; } }
@configuration @conditionalonproperty( prefix = "message.robot", name = {"enable"}, havingvalue = "true", matchifmissing = false ) @conditionalonclass( name = {"ch.qos.logback.classic.loggercontext"} ) @enableconfigurationproperties({robotmessagealarmmessageproperties.class}) @componentscan({"com.xrtframework.logback.alarm"}) public class logbackalarmautoconfiguration { private final robotmessagealarmmessageproperties robotmessagealarmmessageproperties; public logbackalarmautoconfiguration(robotmessagealarmmessageproperties robotmessagealarmmessageproperties) { this.robotmessagealarmmessageproperties = robotmessagealarmmessageproperties; } }
public interface alarmservice { void alarmhandler(string messagetype, string messagecontent); }
public class wechatalarmserviceimpl implements alarmservice { private static final logger log = loggerfactory.getlogger(wechatalarmserviceimpl.class); private string webhookurl; private executorservice executorservice; private static final int corethreadnum = 3; private static final int maxthreadnum = 10; private ratelimiter ratelimiter; private static final double ratelimiterpermitspersecond = 0.2857d; public wechatalarmserviceimpl(string webhookurl) { this.webhookurl = webhookurl; this.executorservice = threadpoolfactory.createexecutorservice(3, 10); this.ratelimiter = ratelimiter.create(ratelimiterpermitspersecond); } public void alarmhandler(string messagetype, string messagecontent) { try { this.ratelimiter.acquire(); this.executorservice.execute(() -> { qiweimessagecontext qiweimessage = new qiweimessagecontext(); qiweimessage.setcontent(messagecontent); qiweimessage.setmentioned_list("@all"); qiweimarkdownmessage<qiweimessagecontext> qiweimessagecontextqiweimarkdownmessage = new qiweimarkdownmessage(); qiweimessagecontextqiweimarkdownmessage.setmsgtype(messagetype); qiweimessagecontextqiweimarkdownmessage.setmarkdown(qiweimessage); if (log.isdebugenabled()) { log.debug("【企业微信机器人告警】消息请求入参:{}", json.tojsonstring(qiweimessagecontextqiweimarkdownmessage)); } string response = okhttputil.builder().postbyjson(this.webhookurl, json.tojsonstring(qiweimessagecontextqiweimarkdownmessage), (map)null); if (log.isdebugenabled()) { log.debug("【企业微信机器人告警】消息请出参:{}", response); } }); } catch (exception var4) { system.out.println(string.format("send wechat error last=%s", var4.getmessage())); } } }
pom.properties version=1.0-snapshot groupid=com.xxframework artifactid=xxframe-logback-alarm
spring-configuration-metadata.json { "groups": [ { "name": "message.robot", "type": "com.xxframework.logback.alarm.config.robotmessagealarmmessageproperties", "sourcetype": "com.xxframework.logback.alarm.config.robotmessagealarmmessageproperties" } ], "properties": [ { "name": "message.robot.enable", "type": "java.lang.string", "sourcetype": "com.xxframework.logback.alarm.config.robotmessagealarmmessageproperties" } ], "hints": [] }
3.pom依赖
<dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter</artifactid> <exclusions> <exclusion> <artifactid>spring-boot-starter-logging</artifactid> <groupid>org.springframework.boot</groupid> </exclusion> </exclusions> </dependency> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> <optional>true</optional> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <!--用来解决未配置spring boot 配置注解处理器idea告警--> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-configuration-processor</artifactid> <optional>true</optional> </dependency> <dependency> <groupid>com.xrtframework</groupid> <artifactid>xrtframe-common-util</artifactid> <version>${project.version}</version> <exclusions> <exclusion> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-logging</artifactid> </exclusion> </exclusions> </dependency> <dependency> <groupid>com.alibaba</groupid> <artifactid>fastjson</artifactid> </dependency> <dependency> <groupid>com.google.guava</groupid> <artifactid>guava</artifactid> <scope>provided</scope> </dependency> </dependencies> <!-- <build>--> <!-- <plugins>--> <!-- <plugin>--> <!-- <groupid>org.springframework.boot</groupid>--> <!-- <artifactid>spring-boot-maven-plugin</artifactid>--> <!-- </plugin>--> <!-- </plugins>--> <!-- --> <!-- </build>--> <build> <plugins> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-compiler-plugin</artifactid> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-surefire-plugin</artifactid> <configuration> <testfailureignore>true</testfailureignore> </configuration> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*</include> </includes> </resource> </resources> </build>
4.主工程引入工具包 pom依赖
<dependency> <groupid>com.xxframework</groupid> <artifactid>xxframe-logback-alarm</artifactid> <version>1.0-snapshot</version> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-actuator</artifactid> </dependency> <!--micrometer桥接prometheus--> <dependency> <groupid>io.micrometer</groupid> <artifactid>micrometer-registry-prometheus</artifactid> </dependency>
5.主工程logback-spring.xml引入配置
<!--添加的内容--> <springproperty scope="context" name="robotmessagechannel" source="message.robot.channel" defaultvalue="qi_wei"/> <springproperty scope="context" name="robotmessageenv" source="message.robot.env" defaultvalue=""/> <springproperty scope="context" name="robotmessagewebhookurl" source="message.robot.webhookurl" defaultvalue=""/> <!--添加的内容--> <appender name="senderrormsg" class="com.xrtframework.logback.alarm.core.sendqiweialarmerrormessageappender"> <!--使用该组件的应用名称 --> <appname>${springappname}</appname> <messagechannel>${robotmessagechannel}</messagechannel> <env>${robotmessageenv}</env> <webhookurl>${robotmessagewebhookurl}</webhookurl> </appender>
6.nacos配置
#应用日志监控 message.robot.sendenabled=true message.robot.channel=qi_wei message.robot.env=测试环境 message.robot.webhookurl=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
到此这篇关于springboot项目编写发送异常日志到企微工具包的文章就介绍到这了,更多相关springboot发送异常日志到企微工具包内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论