提交 0dcaf7ce authored 作者: 吕本才's avatar 吕本才

feat(告警通知): 修改邮件配置为飞书企业邮箱

优化Druid配置默认值和日志配置 清理.gitignore文件并添加.vscode目录
上级 1d4ea963
......@@ -3,8 +3,9 @@ applogs/**
.idea/**
.idea
.history/**
.vscode/**
**/target/
/build/*
/derby.log
**/**.log
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
port: 10001
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.100.40:3306/ruoshui_bigdata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Wxl@325Pa91
# url: jdbc:mysql://127.0.0.1:3306/test_ruoshui_bigdata_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# username: root
# password: 123456
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoshui
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.100.40:3306/ruoshui_bigdata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Wxl@325Pa91
# url: jdbc:mysql://127.0.0.1:3306/test_ruoshui_bigdata_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# username: root
# password: 123456
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoshui
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
# 日志配置
logging:
......
# 开发环境配置
server:
# 服务器的HTTP端口,38环境10001,阿里云 10002
port: 10002
# 服务器的HTTP端口,38环境10001,阿里云 10002
port: 10001
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
# url: jdbc:mysql://rm-2ze28qp55mrm34g8b.mysql.rds.aliyuncs.com:3306/ruoshui_bigdata?keepAlive=true&autoReconnect=true&autoReconnectForPools=true&connectTimeout=3000&socketTimeout=3000
# username: sfabus
# password: Wxl@325Pa91
url: jdbc:mysql://192.168.100.40:3306/ruoshui_bigdata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Wxl@325Pa91
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoshui
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
redis:
# 地址
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
# url: jdbc:mysql://rm-2ze28qp55mrm34g8b.mysql.rds.aliyuncs.com:3306/ruoshui_bigdata?keepAlive=true&autoReconnect=true&autoReconnectForPools=true&connectTimeout=3000&socketTimeout=3000
# username: sfabus
# password: Wxl@325Pa91
url: jdbc:mysql://192.168.100.40:3306/ruoshui_bigdata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Wxl@325Pa91
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoshui
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
redis:
# 地址
# host: r-2zehfktt1r34vn4qws.redis.rds.aliyuncs.com
host: 192.168.100.40
# 端口,默认为6379
port: 6379
# 数据库索引
host: 192.168.100.40
# 端口,默认为6379
port: 6379
# 数据库索引
# database: 3
database: 4
# 密码
database:
4
# 密码
# password: Wxl2025!@#$
password: QjL6H5nH
# 连接超时时间
timeout: 3000s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
password:
QjL6H5nH
# 连接超时时间
timeout: 3000s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle:
0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 开发环境配置
server:
# 服务器的HTTP端口,默认为8080
port: 10001
# 服务器的HTTP端口,默认为8080
port: 10002
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.100.40:3306/ruoshui_bigdata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Wxl@325Pa91
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoshui
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
redis:
# 地址
host: 192.168.100.40
# 端口,默认为6379
port: 6379
# 数据库索引
database: 4
# 密码
password: QjL6H5nH
# 连接超时时间
timeout: 3000s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.100.40:3306/ruoshui_bigdata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: Wxl@325Pa91
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url:
username:
password:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: ruoshui
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
redis:
# 地址
host: 192.168.100.40
# 端口,默认为6379
port: 6379
# 数据库索引
database:
4
# 密码
password:
QjL6H5nH
# 连接超时时间
timeout: 3000s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle:
0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
......@@ -49,11 +49,11 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
# datax-web email
mail:
host: smtp
host: smtp.feishu.cn
port: 465
username: 2217343704@qq.com
password: lsx19980707..
authorization: kwzfbpemiqzwebgf
username: lvbencai@wangxiaolu.com.cn
password: mcRafgxSR1oocC42
authorization: mcRafgxSR1oocC42
properties:
mail:
smtp:
......@@ -71,11 +71,11 @@ spring:
active: druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 500MB
# 设置总上传的文件大小
max-request-size: 1000MB
multipart:
# 单个文件大小
max-file-size: 500MB
# 设置总上传的文件大小
max-request-size: 1000MB
# 服务模块
devtools:
restart:
......@@ -105,33 +105,31 @@ spring:
max-wait: -1ms
# 请求iot平台获取登录LoginId
getIotLoginId:
ip: http://192.168.20.49 #iot平台部署ip
prot: 8080 #iot平台后端访问端口
ip: http://192.168.20.49 #iot平台部署ip
prot: 8080 #iot平台后端访问端口
pathMapping:
userName: admin #账号
password: 123456 #密码
userName: admin #账号
password: 123456 #密码
#iot平台访问地址 会携带LoginId
LoginIot:
ip: http://192.168.20.49 #iot平台部署ip
prot: 10002 #iot平台前端访问端口
ip: http://192.168.20.49 #iot平台部署ip
prot: 10002 #iot平台前端访问端口
# 用户配置
user:
password:
maxRetryCount: 5 # 密码最大错误次数
lockTime: 10 # 密码锁定时间(默认10分钟)
maxRetryCount: 5 # 密码最大错误次数
lockTime: 10 # 密码锁定时间(默认10分钟)
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟)
expireTime: 720
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟)
expireTime: 720
#datax-job, access token
datax:
......@@ -151,7 +149,6 @@ datax:
dataxPyHome: /Users/tumaxiao/soft/大数据etl-datx-flink/datax/bin/datax.py
dataxHome: /Users/tumaxiao/soft/大数据etl-datx-flink/datax
## MyBatis配置
#mybatis:
# # 搜索指定包别名
......@@ -189,7 +186,6 @@ mybatis-plus:
jdbc-type-for-null: 'null'
type-handlers-package: com.ruoshui.core.handler
# PageHelper分页插件
pagehelper:
helperDialect: mysql
......@@ -215,4 +211,7 @@ xss:
datasource:
aes:
key: AD42F6697B035B75
# 飞书配置
feishu:
webhook:
url: https://open.feishu.cn/open-apis/bot/v2/hook/your_webhook_url_here
......@@ -76,16 +76,13 @@
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
......
......@@ -14,15 +14,15 @@ datax:
admin:
### datax admin address list, such as "http://address" or "http://address01,http://address02"
addresses: http://127.0.0.1:10001
# addresses: http://192.168.172.173:8080
# addresses: http://192.168.172.173:8080
executor:
appname: mendale_executor
ip: 127.0.0.1
port: 10002
# port: 9998
# port: 9998
### job log path
logpath: ./applogs/executor/jobhandler
# logpath: /lsx/bigdata/datax-executor/data/applogs/executor/jobhandler
# logpath: /lsx/bigdata/datax-executor/data/applogs/executor/jobhandler
### job log retention days
logretentiondays: 30
### job, access token
......
......@@ -12,37 +12,37 @@ import com.alibaba.druid.pool.DruidDataSource;
@Configuration
public class DruidProperties
{
@Value("${spring.datasource.druid.initialSize}")
@Value("${spring.datasource.druid.initialSize:5}")
private int initialSize;
@Value("${spring.datasource.druid.minIdle}")
@Value("${spring.datasource.druid.minIdle:10}")
private int minIdle;
@Value("${spring.datasource.druid.maxActive}")
@Value("${spring.datasource.druid.maxActive:20}")
private int maxActive;
@Value("${spring.datasource.druid.maxWait}")
@Value("${spring.datasource.druid.maxWait:60000}")
private int maxWait;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis:60000}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis:300000}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
@Value("${spring.datasource.druid.maxEvictableIdleTimeMillis:900000}")
private int maxEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validationQuery}")
@Value("${spring.datasource.druid.validationQuery:SELECT 1 FROM DUAL}")
private String validationQuery;
@Value("${spring.datasource.druid.testWhileIdle}")
@Value("${spring.datasource.druid.testWhileIdle:true}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.testOnBorrow}")
@Value("${spring.datasource.druid.testOnBorrow:false}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.testOnReturn}")
@Value("${spring.datasource.druid.testOnReturn:false}")
private boolean testOnReturn;
public DruidDataSource dataSource(DruidDataSource datasource)
......
......@@ -65,6 +65,9 @@ public class JobAdminConfig implements InitializingBean, DisposableBean {
@Value("${spring.mail.authorization}")
private String emailAuthorization;
@Value("${feishu.webhook.url:}")
private String feishuWebhookUrl;
@Value("${datax.job.triggerpool.fast.max}")
private int triggerPoolFastMax;
......@@ -164,4 +167,8 @@ public class JobAdminConfig implements InitializingBean, DisposableBean {
public String getEmailAuthorization() {
return emailAuthorization;
}
public String getFeishuWebhookUrl() {
return feishuWebhookUrl;
}
}
package com.ruoshui.bigdata.core.thread;
import com.ruoshui.bigdata.core.conf.JobAdminConfig;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FeishuUtil {
private static Logger logger = LoggerFactory.getLogger(FeishuUtil.class);
public static void send(String alarmContent, String title, String content) {
String webhookUrl = JobAdminConfig.getAdminConfig().getFeishuWebhookUrl();
if (webhookUrl == null || webhookUrl.trim().isEmpty()) {
logger.warn(">>>>>>>>>>> ruoshui-ground, feishu webhook url is empty, skip send feishu notification.");
return;
}
try {
JSONObject card = new JSONObject();
card.set("config", new JSONObject().set("wide_screen_mode", true));
JSONObject header = new JSONObject();
JSONObject titleObj = new JSONObject();
titleObj.set("tag", "plain_text");
titleObj.set("content", title);
header.set("title", titleObj);
header.set("template", "red");
card.set("header", header);
JSONObject element = new JSONObject();
element.set("tag", "markdown");
element.set("content", formatMarkdownContent(alarmContent, content));
card.set("elements", new Object[]{element});
JSONObject requestBody = new JSONObject();
requestBody.set("msg_type", "interactive");
requestBody.set("card", card);
String result = HttpUtil.post(webhookUrl, requestBody.toString());
logger.info(">>>>>>>>>>> ruoshui-ground, job fail alarm feishu send result:{}", result);
JSONObject resultJson = JSONUtil.parseObj(result);
if (resultJson != null && resultJson.getInt("code") != null && resultJson.getInt("code") == 0) {
logger.info(">>>>>>>>>>> ruoshui-ground, job fail alarm feishu send success.");
} else {
logger.error(">>>>>>>>>>> ruoshui-ground, job fail alarm feishu send error. Response: {}", result);
}
} catch (Exception e) {
logger.error(">>>>>>>>>>> ruoshui-ground, job fail alarm feishu send exception.", e);
}
}
private static String formatMarkdownContent(String alarmContent, String content) {
StringBuilder markdown = new StringBuilder();
markdown.append("**告警信息**\n");
markdown.append("------------------------------\n");
if (alarmContent != null && !alarmContent.isEmpty()) {
String cleanAlarmContent = alarmContent.replace("<br>", "\n")
.replace("<br/>", "\n")
.replace("<", "&lt;")
.replace(">", "&gt;");
markdown.append(cleanAlarmContent).append("\n");
}
markdown.append("------------------------------\n");
if (content != null && !content.isEmpty()) {
String cleanContent = content.replace("<br>", "\n")
.replace("<br/>", "\n")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replaceAll("<[^>]*>", "");
markdown.append(cleanContent).append("\n");
}
return markdown.toString();
}
}
\ No newline at end of file
......@@ -113,7 +113,7 @@ public class JobFailMonitorHelper {
public void toStop(){
toStop = true;
// interrupt and wait
monitorThread.interrupt();
monitorThread.interrupt();
try {
monitorThread.join();
} catch (InterruptedException e) {
......@@ -184,14 +184,15 @@ public class JobFailMonitorHelper {
try {
EmailUtil.send(JobAdminConfig.getAdminConfig().getEmailUserName(), JobAdminConfig.getAdminConfig().getEmailPassword(),JobAdminConfig.getAdminConfig().getEmailAuthorization(),email,title,content);
} catch (Exception e) {
logger.error(">>>>>>>>>>> ruoshui-ground, job fail alarm email send error, JobLogId:{}", jobLog.getId(), e);
logger.error(">>>>>>>>>>> ruoshui-ground, job fail alarm email send error, JobLogId:"+ jobLog.getId(), e);
alarmResult = false;
}
}
}
// 增加飞书发送通知
// FeishuUtil.send(alarmContent,title,content);
// do something, custom alarm strategy, such as sms
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论