Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
int
Sa Token
提交
6dfa7508
S
Sa Token
项目概览
int
/
Sa Token
9 个月 前同步成功
通知
51
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
Sa Token
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
6dfa7508
编写于
9月 04, 2023
作者:
shengzhang_
提交者:
Gitee
9月 04, 2023
浏览文件
操作
浏览文件
下载
差异文件
!278 Solon 框架升为:2.5.3
Merge pull request !278 from 西东/dev
上级
fcf3cc43
e7616986
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
405 addition
and
52 deletion
+405
-52
sa-token-dependencies/pom.xml
sa-token-dependencies/pom.xml
+2
-2
sa-token-starter/sa-token-solon-plugin/pom.xml
sa-token-starter/sa-token-solon-plugin/pom.xml
+23
-2
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java
...ugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java
+3
-3
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/dao/SaSessionForJacksonCustomized.java
...ev33/satoken/solon/dao/SaSessionForJacksonCustomized.java
+47
-0
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/dao/SaTokenDaoOfRedissonJackson.java
.../dev33/satoken/solon/dao/SaTokenDaoOfRedissonJackson.java
+291
-0
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java
.../cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java
+19
-22
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/sso/SaSsoAutoConfigure.java
...n/java/cn/dev33/satoken/solon/sso/SaSsoAutoConfigure.java
+20
-23
未找到文件。
sa-token-dependencies/pom.xml
浏览文件 @
6dfa7508
...
...
@@ -23,9 +23,9 @@
<servlet-api.version>
3.1.0
</servlet-api.version>
<jakarta-servlet-api.version>
6.0.0
</jakarta-servlet-api.version>
<thymeleaf.version>
3.0.9.RELEASE
</thymeleaf.version>
<solon.version>
2.
4.0
</solon.version>
<solon.version>
2.
5.3
</solon.version>
<noear-redisx.version>
1.4.8
</noear-redisx.version>
<noear-snack3.version>
3.2.7
2
</noear-snack3.version>
<noear-snack3.version>
3.2.7
9
</noear-snack3.version>
<jfinal.version>
4.9.17
</jfinal.version>
<jboot.version>
3.14.4
</jboot.version>
<commons-pool2.version>
2.5.0
</commons-pool2.version>
...
...
sa-token-starter/sa-token-solon-plugin/pom.xml
浏览文件 @
6dfa7508
...
...
@@ -42,15 +42,36 @@
<optional>
true
</optional>
</dependency>
<!-- redisx + snack3 -->
<dependency>
<groupId>
org.noear
</groupId>
<artifactId>
snack3
</artifactId>
<artifactId>
redisx
</artifactId>
<scope>
provided
</scope>
</dependency>
<dependency>
<groupId>
org.noear
</groupId>
<artifactId>
redisx
</artifactId>
<artifactId>
snack3
</artifactId>
<scope>
provided
</scope>
</dependency>
<!-- redisson + jackson -->
<dependency>
<groupId>
org.redisson
</groupId>
<artifactId>
redisson
</artifactId>
<scope>
provided
</scope>
</dependency>
<!-- jackson-databind -->
<dependency>
<groupId>
com.fasterxml.jackson.core
</groupId>
<artifactId>
jackson-databind
</artifactId>
<scope>
provided
</scope>
</dependency>
<!-- jackson-datatype-jsr310 -->
<dependency>
<groupId>
com.fasterxml.jackson.datatype
</groupId>
<artifactId>
jackson-datatype-jsr310
</artifactId>
<scope>
provided
</scope>
</dependency>
...
...
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/XPluginImp.java
浏览文件 @
6dfa7508
...
...
@@ -35,7 +35,7 @@ import cn.dev33.satoken.stp.StpLogic;
import
cn.dev33.satoken.stp.StpUtil
;
import
cn.dev33.satoken.temp.SaTempInterface
;
import
org.noear.solon.Solon
;
import
org.noear.solon.core.A
o
pContext
;
import
org.noear.solon.core.A
p
pContext
;
import
org.noear.solon.core.Plugin
;
/**
...
...
@@ -45,7 +45,7 @@ import org.noear.solon.core.Plugin;
public
class
XPluginImp
implements
Plugin
{
@Override
public
void
start
(
A
o
pContext
context
)
{
public
void
start
(
A
p
pContext
context
)
{
// Sa-Token 日志输出 Bean
context
.
getBeanAsync
(
SaLog
.
class
,
bean
->
{
SaManager
.
setLog
(
bean
);
...
...
@@ -60,7 +60,7 @@ public class XPluginImp implements Plugin {
});
}
private
void
beanInitDo
(
A
o
pContext
context
)
{
private
void
beanInitDo
(
A
p
pContext
context
)
{
// 注入上下文Bean
SaManager
.
setSaTokenContext
(
new
SaContextForSolon
());
...
...
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/dao/SaSessionForJacksonCustomized.java
0 → 100644
浏览文件 @
6dfa7508
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
cn.dev33.satoken.solon.dao
;
import
cn.dev33.satoken.session.SaSession
;
import
com.fasterxml.jackson.annotation.JsonIgnoreProperties
;
/**
* Jackson定制版SaSession,忽略 timeout 等属性的序列化
*
* @author click33
* @since 1.34.0
*/
@JsonIgnoreProperties
({
"timeout"
})
public
class
SaSessionForJacksonCustomized
extends
SaSession
{
/**
*
*/
private
static
final
long
serialVersionUID
=
-
7600983549653130681L
;
public
SaSessionForJacksonCustomized
()
{
super
();
}
/**
* 构建一个Session对象
* @param id Session的id
*/
public
SaSessionForJacksonCustomized
(
String
id
)
{
super
(
id
);
}
}
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/dao/SaTokenDaoOfRedissonJackson.java
0 → 100644
浏览文件 @
6dfa7508
/*
* Copyright 2020-2099 sa-token.cc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
cn.dev33.satoken.solon.dao
;
import
cn.dev33.satoken.dao.SaTokenDao
;
import
cn.dev33.satoken.strategy.SaStrategy
;
import
cn.dev33.satoken.util.SaFoxUtil
;
import
com.fasterxml.jackson.annotation.JsonTypeInfo
;
import
com.fasterxml.jackson.databind.DeserializationFeature
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
;
import
com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
;
import
com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer
;
import
com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer
;
import
com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer
;
import
com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer
;
import
com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer
;
import
org.redisson.api.RBatch
;
import
org.redisson.api.RBucket
;
import
org.redisson.api.RBucketAsync
;
import
org.redisson.api.RedissonClient
;
import
org.redisson.client.codec.Codec
;
import
org.redisson.codec.JsonJacksonCodec
;
import
java.time.Duration
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
import
java.time.LocalTime
;
import
java.time.format.DateTimeFormatter
;
import
java.util.List
;
import
java.util.stream.Collectors
;
import
java.util.stream.Stream
;
/**
* Sa-Token 持久层实现 [ Redisson客户端、Redis存储、Jackson序列化 ]
*
* @author 疯狂的狮子Li
* @author noear
* @since 1.34.0
*/
public
class
SaTokenDaoOfRedissonJackson
implements
SaTokenDao
{
public
static
final
String
DATE_TIME_PATTERN
=
"yyyy-MM-dd HH:mm:ss"
;
public
static
final
String
DATE_PATTERN
=
"yyyy-MM-dd"
;
public
static
final
String
TIME_PATTERN
=
"HH:mm:ss"
;
public
static
final
DateTimeFormatter
DATE_TIME_FORMATTER
=
DateTimeFormatter
.
ofPattern
(
DATE_TIME_PATTERN
);
public
static
final
DateTimeFormatter
DATE_FORMATTER
=
DateTimeFormatter
.
ofPattern
(
DATE_PATTERN
);
public
static
final
DateTimeFormatter
TIME_FORMATTER
=
DateTimeFormatter
.
ofPattern
(
TIME_PATTERN
);
/**
* ObjectMapper 对象 (以 public 作用域暴露出此对象,方便开发者二次更改配置)
*
* <p> 例如:
* <pre>
* SaTokenDaoRedisJackson redisJackson = (SaTokenDaoRedisJackson) SaManager.getSaTokenDao();
* redisJackson.objectMapper.xxx = xxx;
* </pre>
* </p>
*/
public
final
ObjectMapper
objectMapper
;
/**
* 序列化方式
*/
public
final
Codec
codec
;
/**
* redisson 客户端
*/
public
final
RedissonClient
redissonClient
;
public
SaTokenDaoOfRedissonJackson
(
RedissonClient
redissonClient
)
{
this
.
objectMapper
=
new
ObjectMapper
();
this
.
objectMapper
.
enableDefaultTyping
(
ObjectMapper
.
DefaultTyping
.
NON_FINAL
,
JsonTypeInfo
.
As
.
PROPERTY
);
// 配置[忽略未知字段]
this
.
objectMapper
.
configure
(
DeserializationFeature
.
FAIL_ON_UNKNOWN_PROPERTIES
,
false
);
// 配置[时间类型转换]
JavaTimeModule
timeModule
=
new
JavaTimeModule
();
// LocalDateTime序列化与反序列化
timeModule
.
addSerializer
(
new
LocalDateTimeSerializer
(
DATE_TIME_FORMATTER
));
timeModule
.
addDeserializer
(
LocalDateTime
.
class
,
new
LocalDateTimeDeserializer
(
DATE_TIME_FORMATTER
));
// LocalDate序列化与反序列化
timeModule
.
addSerializer
(
new
LocalDateSerializer
(
DATE_FORMATTER
));
timeModule
.
addDeserializer
(
LocalDate
.
class
,
new
LocalDateDeserializer
(
DATE_FORMATTER
));
// LocalTime序列化与反序列化
timeModule
.
addSerializer
(
new
LocalTimeSerializer
(
TIME_FORMATTER
));
timeModule
.
addDeserializer
(
LocalTime
.
class
,
new
LocalTimeDeserializer
(
TIME_FORMATTER
));
this
.
objectMapper
.
registerModule
(
timeModule
);
// 重写 SaSession 生成策略
SaStrategy
.
instance
.
createSession
=
(
sessionId
)
->
new
SaSessionForJacksonCustomized
(
sessionId
);
// 开始初始化相关组件
this
.
codec
=
new
JsonJacksonCodec
(
objectMapper
);
this
.
redissonClient
=
redissonClient
;
}
/**
* 获取Value,如无返空
*/
@Override
public
String
get
(
String
key
)
{
RBucket
<
String
>
rBucket
=
redissonClient
.
getBucket
(
key
,
codec
);
return
rBucket
.
get
();
}
/**
* 写入Value,并设定存活时间 (单位: 秒)
*/
@Override
public
void
set
(
String
key
,
String
value
,
long
timeout
)
{
if
(
timeout
==
0
||
timeout
<=
SaTokenDao
.
NOT_VALUE_EXPIRE
)
{
return
;
}
// 判断是否为永不过期
if
(
timeout
==
SaTokenDao
.
NEVER_EXPIRE
)
{
RBucket
<
String
>
bucket
=
redissonClient
.
getBucket
(
key
,
codec
);
bucket
.
set
(
value
);
}
else
{
RBatch
batch
=
redissonClient
.
createBatch
();
RBucketAsync
<
String
>
bucket
=
batch
.
getBucket
(
key
,
codec
);
bucket
.
setAsync
(
value
);
bucket
.
expireAsync
(
Duration
.
ofSeconds
(
timeout
));
batch
.
execute
();
}
}
/**
* 修修改指定key-value键值对 (过期时间不变)
*/
@Override
public
void
update
(
String
key
,
String
value
)
{
long
expire
=
getTimeout
(
key
);
// -2 = 无此键
if
(
expire
==
SaTokenDao
.
NOT_VALUE_EXPIRE
)
{
return
;
}
this
.
set
(
key
,
value
,
expire
);
}
/**
* 删除Value
*/
@Override
public
void
delete
(
String
key
)
{
redissonClient
.
getBucket
(
key
,
codec
).
delete
();
}
/**
* 获取Value的剩余存活时间 (单位: 秒)
*/
@Override
public
long
getTimeout
(
String
key
)
{
RBucket
<
String
>
rBucket
=
redissonClient
.
getBucket
(
key
,
codec
);
long
timeout
=
rBucket
.
remainTimeToLive
();
return
timeout
<
0
?
timeout
:
timeout
/
1000
;
}
/**
* 修改Value的剩余存活时间 (单位: 秒)
*/
@Override
public
void
updateTimeout
(
String
key
,
long
timeout
)
{
// 判断是否想要设置为永久
if
(
timeout
==
SaTokenDao
.
NEVER_EXPIRE
)
{
long
expire
=
getTimeout
(
key
);
if
(
expire
==
SaTokenDao
.
NEVER_EXPIRE
)
{
// 如果其已经被设置为永久,则不作任何处理
}
else
{
// 如果尚未被设置为永久,那么再次set一次
this
.
set
(
key
,
this
.
get
(
key
),
timeout
);
}
return
;
}
RBucket
<
String
>
rBucket
=
redissonClient
.
getBucket
(
key
,
codec
);
rBucket
.
expire
(
Duration
.
ofSeconds
(
timeout
));
}
/**
* 获取Object,如无返空
*/
@Override
public
Object
getObject
(
String
key
)
{
RBucket
<
Object
>
rBucket
=
redissonClient
.
getBucket
(
key
,
codec
);
return
rBucket
.
get
();
}
/**
* 写入Object,并设定存活时间 (单位: 秒)
*/
@Override
public
void
setObject
(
String
key
,
Object
object
,
long
timeout
)
{
if
(
timeout
==
0
||
timeout
<=
SaTokenDao
.
NOT_VALUE_EXPIRE
)
{
return
;
}
// 判断是否为永不过期
if
(
timeout
==
SaTokenDao
.
NEVER_EXPIRE
)
{
RBucket
<
Object
>
bucket
=
redissonClient
.
getBucket
(
key
,
codec
);
bucket
.
set
(
object
);
}
else
{
RBatch
batch
=
redissonClient
.
createBatch
();
RBucketAsync
<
Object
>
bucket
=
batch
.
getBucket
(
key
,
codec
);
bucket
.
setAsync
(
object
);
bucket
.
expireAsync
(
Duration
.
ofSeconds
(
timeout
));
batch
.
execute
();
}
}
/**
* 更新Object (过期时间不变)
*/
@Override
public
void
updateObject
(
String
key
,
Object
object
)
{
long
expire
=
getObjectTimeout
(
key
);
// -2 = 无此键
if
(
expire
==
SaTokenDao
.
NOT_VALUE_EXPIRE
)
{
return
;
}
this
.
setObject
(
key
,
object
,
expire
);
}
/**
* 删除Object
*/
@Override
public
void
deleteObject
(
String
key
)
{
redissonClient
.
getBucket
(
key
,
codec
).
delete
();
}
/**
* 获取Object的剩余存活时间 (单位: 秒)
*/
@Override
public
long
getObjectTimeout
(
String
key
)
{
RBucket
<
String
>
rBucket
=
redissonClient
.
getBucket
(
key
,
codec
);
long
timeout
=
rBucket
.
remainTimeToLive
();
return
timeout
<
0
?
timeout
:
timeout
/
1000
;
}
/**
* 修改Object的剩余存活时间 (单位: 秒)
*/
@Override
public
void
updateObjectTimeout
(
String
key
,
long
timeout
)
{
// 判断是否想要设置为永久
if
(
timeout
==
SaTokenDao
.
NEVER_EXPIRE
)
{
long
expire
=
getObjectTimeout
(
key
);
if
(
expire
==
SaTokenDao
.
NEVER_EXPIRE
)
{
// 如果其已经被设置为永久,则不作任何处理
}
else
{
// 如果尚未被设置为永久,那么再次set一次
this
.
setObject
(
key
,
this
.
getObject
(
key
),
timeout
);
}
return
;
}
RBucket
<
Object
>
rBucket
=
redissonClient
.
getBucket
(
key
,
codec
);
rBucket
.
expire
(
Duration
.
ofSeconds
(
timeout
));
}
/**
* 搜索数据
*/
@Override
public
List
<
String
>
searchData
(
String
prefix
,
String
keyword
,
int
start
,
int
size
,
boolean
sortType
)
{
Stream
<
String
>
stream
=
redissonClient
.
getKeys
().
getKeysStreamByPattern
(
prefix
+
"*"
+
keyword
+
"*"
);
List
<
String
>
list
=
stream
.
collect
(
Collectors
.
toList
());
return
SaFoxUtil
.
searchList
(
list
,
start
,
size
,
sortType
);
}
}
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/oauth2/SaOAuth2AutoConfigure.java
浏览文件 @
6dfa7508
...
...
@@ -23,39 +23,36 @@ import org.noear.solon.annotation.Bean;
import
org.noear.solon.annotation.Condition
;
import
org.noear.solon.annotation.Configuration
;
import
org.noear.solon.annotation.Inject
;
import
org.noear.solon.core.AppContext
;
import
org.noear.solon.core.bean.InitializingBean
;
/**
* @author noear
* @since 2.0
*/
@Condition
(
onClass
=
SaOAuth2Manager
.
class
)
@Configuration
public
class
SaOAuth2AutoConfigure
{
/**
* 获取 OAuth2配置Bean
*/
@Bean
public
SaOAuth2Config
getConfig
(
@Inject
(
value
=
"${sa-token.oauth2}"
,
required
=
false
)
SaOAuth2Config
oAuth2Config
)
{
return
oAuth2Config
;
}
public
class
SaOAuth2AutoConfigure
implements
InitializingBean
{
@Inject
private
AppContext
appContext
;
/**
* 注入OAuth2配置Bean
*
* @param saOAuth2Config 配置对象
*/
@Bean
public
void
setSaOAuth2Config
(
@Inject
(
required
=
false
)
SaOAuth2Config
saOAuth2Config
)
{
SaOAuth2Manager
.
setConfig
(
saOAuth2Config
);
@Override
public
void
afterInjection
()
throws
Throwable
{
appContext
.
subBeansOfType
(
SaOAuth2Template
.
class
,
bean
->
{
SaOAuth2Util
.
saOAuth2Template
=
bean
;
});
appContext
.
subBeansOfType
(
SaOAuth2Config
.
class
,
bean
->
{
SaOAuth2Manager
.
setConfig
(
bean
);
});
}
/**
* 注入代码模板Bean
*
* @param saOAuth2Template 代码模板Bean
* 获取 OAuth2配置Bean
*/
@Bean
public
void
setSaOAuth2Interface
(
@Inject
(
required
=
false
)
SaOAuth2Template
saOAuth2Template
)
{
SaOAuth2Util
.
saOAuth2Template
=
saOAuth2Template
;
public
SaOAuth2Config
getConfig
(
@Inject
(
value
=
"${sa-token.oauth2}"
,
required
=
false
)
SaOAuth2Config
oAuth2Config
)
{
return
oAuth2Config
;
}
}
}
\ No newline at end of file
sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/sso/SaSsoAutoConfigure.java
浏览文件 @
6dfa7508
...
...
@@ -24,14 +24,32 @@ import org.noear.solon.annotation.Bean;
import
org.noear.solon.annotation.Condition
;
import
org.noear.solon.annotation.Configuration
;
import
org.noear.solon.annotation.Inject
;
import
org.noear.solon.core.AppContext
;
import
org.noear.solon.core.bean.InitializingBean
;
/**
* @author noear
* @since 2.0
*/
@Condition
(
onClass
=
SaSsoManager
.
class
)
@Configuration
public
class
SaSsoAutoConfigure
{
public
class
SaSsoAutoConfigure
implements
InitializingBean
{
@Inject
private
AppContext
appContext
;
@Override
public
void
afterInjection
()
throws
Throwable
{
appContext
.
subBeansOfType
(
SaSsoTemplate
.
class
,
bean
->{
SaSsoUtil
.
ssoTemplate
=
bean
;
SaSsoProcessor
.
instance
.
ssoTemplate
=
bean
;
});
appContext
.
subBeansOfType
(
SaSsoConfig
.
class
,
bean
->{
SaSsoManager
.
setConfig
(
bean
);
});
}
/**
* 获取 SSO 配置Bean
* */
...
...
@@ -39,25 +57,4 @@ public class SaSsoAutoConfigure {
public
SaSsoConfig
getConfig
(
@Inject
(
value
=
"${sa-token.sso}"
,
required
=
false
)
SaSsoConfig
ssoConfig
)
{
return
ssoConfig
;
}
/**
* 注入 Sa-Token-SSO 配置Bean
*
* @param saSsoConfig 配置对象
*/
@Bean
public
void
setSaSsoConfig
(
@Inject
(
required
=
false
)
SaSsoConfig
saSsoConfig
)
{
SaSsoManager
.
setConfig
(
saSsoConfig
);
}
/**
* 注入 Sa-Token-SSO 单点登录模块 Bean
*
* @param ssoTemplate saSsoTemplate对象
*/
@Bean
public
void
setSaSsoTemplate
(
@Inject
(
required
=
false
)
SaSsoTemplate
ssoTemplate
)
{
SaSsoUtil
.
ssoTemplate
=
ssoTemplate
;
SaSsoProcessor
.
instance
.
ssoTemplate
=
ssoTemplate
;
}
}
}
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录