提交 d7223463 编写于 作者: sq3536's avatar sq3536

bugfix

上级 9cd4063c
......@@ -6,7 +6,6 @@ spring:
main:
allow-bean-definition-overriding: true
server:
port: 8080
servlet:
contextPath: /{{lowerCase system.name}}
......
......@@ -34,7 +34,6 @@ import {{packageName}}.core.{{entity.module}}.domain.{{entity.codeName}};
import {{packageName}}.core.{{entity.module}}.filter.{{entity.codeName}}SearchContext;
import {{packageName}}.core.{{entity.module}}.service.{{entity.codeName}}Service;
import cn.ibizlab.util.helper.CachedBeanCopier;
import cn.ibizlab.util.helper.DEFieldCacheMap;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
......
......@@ -36,7 +36,6 @@ import {{packageName}}.core.{{entity.module}}.filter.{{entity.codeName}}SearchCo
import {{packageName}}.core.{{entity.module}}.service.{{entity.codeName}}Service;
import {{packageName}}.core.{{entity.module}}.mapper.{{entity.codeName}}Mapper;
import cn.ibizlab.util.helper.CachedBeanCopier;
import cn.ibizlab.util.helper.DEFieldCacheMap;
import cn.ibizlab.util.security.AuthenticationUser;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
......
......@@ -34,7 +34,6 @@ import {{packageName}}.core.{{entity.module}}.domain.{{entity.codeName}};
import {{packageName}}.core.{{entity.module}}.filter.{{entity.codeName}}SearchContext;
import {{packageName}}.core.{{entity.module}}.service.{{entity.codeName}}Service;
import cn.ibizlab.util.helper.CachedBeanCopier;
import cn.ibizlab.util.helper.DEFieldCacheMap;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
......
package {{packageName}}.util.aspect;
import cn.ibizlab.util.helper.BeanCache;
import lombok.SneakyThrows;
import cn.ibizlab.util.annotation.Audit;
import cn.ibizlab.util.domain.EntityBase;
import cn.ibizlab.util.helper.DEFieldCacheMap;
import cn.ibizlab.util.service.IBZDataAuditService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
......@@ -56,18 +56,9 @@ public class AuditAspect
Object serviceParam = args[0];
if(serviceParam instanceof EntityBase) {
EntityBase entity = (EntityBase)serviceParam;
Map<String, Audit> auditFields = DEFieldCacheMap.getAuditFields(entity.getClass());
//是否有审计属性
if(auditFields.size()==0) {
return;
}
String idField = DEFieldCacheMap.getDEKeyField(entity.getClass());
Object idValue = "";
if(!StringUtils.isEmpty(idField)) {
idValue=entity.get(idField);
}
//记录审计日志
dataAuditService.createAudit(request, entity, idValue, auditFields);
if(BeanCache.hasAudit(entity.getClass()))
dataAuditService.createAudit(request, entity, null);
}
}
......@@ -91,12 +82,11 @@ public class AuditAspect
Object arg = args[0];
if(arg instanceof EntityBase) {
EntityBase entity = (EntityBase) arg;
Map<String, Audit> auditFields = DEFieldCacheMap.getAuditFields(entity.getClass());
//是否有审计属性
if(auditFields.size()==0) {
return point.proceed();
}
String idField = DEFieldCacheMap.getDEKeyField(entity.getClass());
if(BeanCache.hasAudit(entity.getClass()))
{
String idField = BeanCache.getKeyField(entity.getClass());
Object idValue = "";
if(!StringUtils.isEmpty(idField)){
idValue = entity.get(idField);
......@@ -105,50 +95,21 @@ public class AuditAspect
return point.proceed();
}
//获取更新前实体
EntityBase beforeEntity = getEntity(serviceObj, idValue);
EntityBase beforeEntity = getEntity(serviceObj, idValue, entity.getClass());
//执行更新操作
point.proceed();
//记录审计日志
dataAuditService.updateAudit(request, beforeEntity, serviceObj, idValue, auditFields);
return true;
}
return point.proceed();
}
/**
* 实体数据更新切面,在成功更新数据后将新增数据内容记录审计日志内(审计明细【AuditInfo】中只记录审计属性变化情况,审计属性在平台属性中配置)
* 使用环切【@Around】获取要删除的完整数据,并将审计属性相关信息记录到审计日志中
* @param point
* @return
* @throws Throwable
*/
@Around("execution(* {{packageName}}.core.*.service.*.remove(..)){{#system.enableES}}&& !execution(* {{packageName}}.core.es.service.*.remove*(..)){{/system.enableES}}")
public Object remove(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = null;
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if(requestAttributes!= null) {
request = ((ServletRequestAttributes)requestAttributes).getRequest();
}
Object serviceObj = point.getTarget();
Object args[] = point.getArgs();
if(ObjectUtils.isEmpty(args) || args.length==0) {
return point.proceed();
}
Object idValue = args[0];
EntityBase entity = getEntity(serviceObj, idValue);
Map<String, Audit> auditFields = DEFieldCacheMap.getAuditFields(entity.getClass());
if(auditFields.size()==0) {
return point.proceed();
dataAuditService.updateAudit(request, beforeEntity , entity, null);
}
else{
//执行删除操作
else
point.proceed();
//记录审计日志
dataAuditService.removeAudit(request, entity, idValue, auditFields);
return true;
}
return point.proceed();
}
/**
* 获取实体
* @param service
......@@ -156,15 +117,14 @@ public class AuditAspect
* @return
*/
@SneakyThrows
private EntityBase getEntity(Object service, Object id) {
EntityBase entity = null;
private <T extends EntityBase> T getEntity(Object service, Object id, Class<T> clazz) {
if(!ObjectUtils.isEmpty(service)) {
EvaluationContext oldContext = new StandardEvaluationContext();
oldContext.setVariable("service", service);
oldContext.setVariable("id", id);
Expression oldExp = parser.parseExpression("#service.get(#id)");
return oldExp.getValue(oldContext, EntityBase.class);
return oldExp.getValue(oldContext, clazz);
}
return entity;
return null;
}
}
\ No newline at end of file
......@@ -4,7 +4,7 @@ import cn.ibizlab.util.annotation.DEField;
import cn.ibizlab.util.domain.EntityBase;
import cn.ibizlab.util.enums.DEFieldDefaultValueType;
import cn.ibizlab.util.enums.DEPredefinedFieldType;
import cn.ibizlab.util.helper.DEFieldCacheMap;
import cn.ibizlab.util.helper.BeanCache;
import cn.ibizlab.util.security.AuthenticationUser;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
......@@ -91,24 +91,23 @@ public class DEFieldDefaultValueAspect
Object obj = args[0];
String actionName = joinPoint.getSignature().getName();
if(obj instanceof EntityBase) {
Map<String, DEField> deFields = DEFieldCacheMap.getDEFields(obj.getClass());
AuthenticationUser curUser = AuthenticationUser.getAuthenticationUser();
String keyField = DEFieldCacheMap.getDEKeyField(obj.getClass());
String keyField = BeanCache.getKeyField(obj.getClass());
if(StringUtils.isEmpty(keyField)) {
return true;
}
fillDEField((EntityBase)obj, deFields, actionName, curUser, keyField);
fillDEField((EntityBase)obj, BeanCache.get(obj.getClass()).getDeFields(), actionName, curUser, keyField);
}
else if (obj instanceof List) {
Map<String, DEField> deFields = null;
List<BeanCache.FieldItem> deFields = null;
AuthenticationUser curUser = null;
String keyField = "";
for(Object item : (List)obj) {
if(item instanceof EntityBase) {
if(deFields == null) {
deFields = DEFieldCacheMap.getDEFields(item.getClass());
deFields = BeanCache.get(item.getClass()).getDeFields();
curUser = AuthenticationUser.getAuthenticationUser();
keyField = DEFieldCacheMap.getDEKeyField(item.getClass());
keyField = BeanCache.getKeyField(item.getClass());
if(StringUtils.isEmpty(keyField)) {
return true;
}
......@@ -127,7 +126,7 @@ public class DEFieldDefaultValueAspect
* 填充系统预置属性
* @param et 当前实体对象
*/
private void fillDEField(EntityBase et, Map<String, DEField> deFields, String actionName, AuthenticationUser curUser, String keyField) throws Exception {
private void fillDEField(EntityBase et, List<BeanCache.FieldItem> deFields, String actionName, AuthenticationUser curUser, String keyField) throws Exception {
if(deFields.size()==0) {
return;
}
......@@ -136,10 +135,10 @@ public class DEFieldDefaultValueAspect
actionName="create";
}
}
for (Map.Entry<String, DEField> entry : deFields.entrySet()) {
String fieldname=entry.getKey();
for (BeanCache.FieldItem item : deFields) {
String fieldname=item.getCodeName();
//获取注解
DEField fieldAnnotation=entry.getValue();
DEField fieldAnnotation=item.getDeField();
//获取默认值类型
DEFieldDefaultValueType deFieldType=fieldAnnotation.defaultValueType();
//获取属性默认值
......
package {{packageName}}.util.aspect;
import cn.ibizlab.util.helper.BeanCache;
import lombok.extern.slf4j.Slf4j;
import cn.ibizlab.util.domain.DELogic;
import cn.ibizlab.util.domain.EntityBase;
import cn.ibizlab.util.errors.BadRequestAlertException;
import cn.ibizlab.util.helper.DEFieldCacheMap;
import org.apache.commons.io.IOUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
......@@ -78,7 +78,7 @@ public class DELogicAspect {
if (("remove".equalsIgnoreCase(action) || "get".equalsIgnoreCase(action)) && (!(arg instanceof EntityBase))) {
entity = getEntity(service.getClass());
if(!ObjectUtils.isEmpty(entity)) {
String id = DEFieldCacheMap.getDEKeyField(entity.getClass());
String id = BeanCache.getKeyField(entity.getClass());
if(StringUtils.isEmpty(id)) {
log.debug("无法获取实体主键属性[{}]",getEntityName(entity));
return point.proceed();
......
package {{packageName}}.util.aspect;
import cn.ibizlab.util.helper.BeanCache;
import lombok.extern.slf4j.Slf4j;
import cn.ibizlab.util.annotation.DEField;
import cn.ibizlab.util.enums.DupCheck;
import cn.ibizlab.util.errors.BadRequestAlertException;
import cn.ibizlab.util.filter.QueryFilter;
import cn.ibizlab.util.filter.SearchContextBase;
import cn.ibizlab.util.helper.DEFieldCacheMap;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
......@@ -56,10 +56,11 @@ public class DupCheckAspect {
if (args.length > 0) {
Object entity = args[0];
Object service = point.getTarget();
Map<String, DEField> deFields = DEFieldCacheMap.getDEFields(entity.getClass());
for (Map.Entry<String, DEField> deField : deFields.entrySet()) {
String fieldName = deField.getKey();
DEField fieldAnnotation = deField.getValue();
BeanCache.get(entity.getClass()).getDeFields().forEach(item->{
String fieldName = item.getFieldName();
DEField fieldAnnotation = item.getDeField();
DupCheck dupCheck = fieldAnnotation.dupCheck();
String dupCheckField=fieldAnnotation.dupCheckField();
if (dupCheck == DupCheck.ALL) {
......@@ -90,7 +91,7 @@ public class DupCheckAspect {
throw new BadRequestAlertException(String.format("数据保存失败,属性[%s]:值[%s]已存在!", fieldName, newValue), "DupCheckAspect", "DupCheck");
}
}
}
});
}
}
......@@ -115,10 +116,10 @@ public class DupCheckAspect {
*/
private void setValue(Object entity , QueryFilter filter, String fieldName, Object value){
if(ObjectUtils.isEmpty(value)) {
filter.isnull(DEFieldCacheMap.getFieldColumnName(entity.getClass(), fieldName));
filter.isnull(BeanCache.getFieldName(entity.getClass(), fieldName));
}
else {
filter.eq(DEFieldCacheMap.getFieldColumnName(entity.getClass(), fieldName), value);
filter.eq(BeanCache.getFieldName(entity.getClass(), fieldName), value);
}
}
}
\ No newline at end of file
package {{packageName}}.util.security;
import cn.ibizlab.util.helper.BeanCache;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.SneakyThrows;
import cn.ibizlab.util.annotation.DEField;
import cn.ibizlab.util.domain.EntityBase;
import cn.ibizlab.util.enums.DEPredefinedFieldType;
import cn.ibizlab.util.filter.QueryWrapperContext;
import cn.ibizlab.util.helper.DEFieldCacheMap;
import cn.ibizlab.util.security.AuthenticationUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.PermissionEvaluator;
......@@ -111,10 +111,11 @@ public class AuthPermissionEvaluator implements PermissionEvaluator {
if(entity==null){
return ;
}
Map<String,String> permissionField=getPermissionField(entity);//获取组织、部门预置属性
String orgField=permissionField.get("orgfield");
String orgDeptField=permissionField.get("orgsecfield");
String createManField=permissionField.get("createmanfield");
BeanCache.BeanSchema schema=BeanCache.get(entity.getClass());
String orgField=(schema.getOrgField()!=null)?schema.getOrgField().getFieldName():null;
String orgDeptField=(schema.getOrgDeptField()!=null)?schema.getOrgDeptField().getFieldName():null;
String createManField=(schema.getCreateManField()!=null)?schema.getCreateManField().getFieldName():null;;
Map<String, Set<String>> userInfo = authenticationUser.getOrgInfo();
Set<String> orgParent = userInfo.get("parentorg");
Set<String> orgChild = userInfo.get("suborg");
......@@ -152,19 +153,19 @@ public class AuthPermissionEvaluator implements PermissionEvaluator {
}
else{
Consumer<QueryWrapper> consumer = qw -> {
if(userOrg.size()>0){
if((!ObjectUtils.isEmpty(orgField))&&userOrg.size()>0){
Consumer<QueryWrapper> org = orgQw -> {
orgQw.in(orgField,userOrg);
};
qw.or(org);
}
if(userOrgDept.size()>0){
if((!ObjectUtils.isEmpty(orgDeptField))&&userOrgDept.size()>0){
Consumer<QueryWrapper> dept = deptQw -> {
deptQw.in(orgDeptField,userOrgDept);
};
qw.or(dept);
}
if(userCreateMan.size()>0){
if((!ObjectUtils.isEmpty(createManField))&&userCreateMan.size()>0){
Consumer<QueryWrapper> createMan = createManQw -> {
createManQw.eq(createManField,authenticationUser.getUserid());
};
......@@ -222,19 +223,23 @@ public class AuthPermissionEvaluator implements PermissionEvaluator {
*/
private boolean actionValid(EntityBase entity, String action , Set<String> userAuthorities ,AuthenticationUser authenticationUser){
Map<String,String> permissionField=getPermissionField(entity);//获取组织、部门预置属性
String orgField=permissionField.get("orgfield");
String orgDeptField=permissionField.get("orgsecfield");
String createManField=permissionField.get("createmanfield");
Map<String, Set<String>> userInfo = authenticationUser.getOrgInfo();
Set<String> orgParent = userInfo.get("parentorg");
Set<String> orgChild = userInfo.get("suborg");
Set<String> orgDeptParent = userInfo.get("parentdept");
Set<String> orgDeptChild = userInfo.get("subdept");
Object orgFieldValue=entity.get(orgField);
Object orgDeptFieldValue=entity.get(orgDeptField);
Object crateManFieldValue=entity.get(createManField);
Object orgFieldValue=null;
BeanCache.BeanSchema schema=BeanCache.get(entity.getClass());
if(schema.getOrgField()!=null)
orgFieldValue=entity.get(schema.getOrgField().getCodeName());
Object orgDeptFieldValue=null;
if(schema.getOrgDeptField()!=null)
orgDeptFieldValue=entity.get(schema.getOrgDeptField().getCodeName());
Object crateManFieldValue=null;
if(schema.getCreateManField()!=null)
crateManFieldValue=entity.get(schema.getCreateManField().getCodeName());
Set<String> userOrg = new HashSet<>();
Set<String> userOrgDept = new HashSet<>();
......@@ -286,43 +291,5 @@ public class AuthPermissionEvaluator implements PermissionEvaluator {
}
}
/**
* 获取实体权限字段 orgid/orgsecid
* @param entityBase
* @return
*/
private Map<String,String> getPermissionField(EntityBase entityBase){
Map<String,String> permissionFiled=new HashMap<>();
String orgField="orgid"; //组织属性
String orgDeptField="orgsectorid"; //部门属性
String createManField="createman"; //创建人属性
DEFieldCacheMap.getFieldMap(entityBase.getClass().getName());
Map <String, DEField> preFields= DEFieldCacheMap.getDEFields(entityBase.getClass()); //从缓存中获取当前类预置属性
for (Map.Entry<String,DEField> entry : preFields.entrySet()){
DEField fieldAnnotation=entry.getValue();//获取注解值
String fieldName=fieldAnnotation.name();//获取注解字段
DEPredefinedFieldType prefieldType=fieldAnnotation.preType();
if(!StringUtils.isEmpty(fieldName)){
//用户配置系统预置属性-组织机构标识
if(prefieldType==prefieldType.ORGID){
orgField=fieldName;
}
//用户配置系统预置属性-部门标识
if(prefieldType==prefieldType.ORGSECTORID){
orgDeptField=fieldName;
}
//用户配置系统预置属性-部门标识
if(prefieldType==prefieldType.CREATEMAN){
createManField=fieldName;
}
}
}
permissionFiled.put("orgfield",orgField);
permissionFiled.put("orgsecfield",orgDeptField);
permissionFiled.put("createmanfield",createManField);
return permissionFiled;
}
}
\ No newline at end of file
#eureka配置中心
spring:
cloud:
nacos:
discovery:
enabled: false
eureka:
client:
enabled: true
serviceUrl:
defaultZone: http://127.0.0.1:8762/eureka/
\ No newline at end of file
#nacos配置中心
spring:
cloud:
nacos:
discovery:
server-addr: 172.16.100.77:8848
enabled: true
eureka:
client:
enabled: false
\ No newline at end of file
#缓存、数据源
spring:
cache:
redis:
time-to-live: 3600
caffeine:
spec: initialCapacity=5,maximumSize=500,expireAfterWrite=3600s
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
lettuce:
pool:
max-active: 32
max-wait: 300ms
max-idle: 16
min-idle: 8
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
{{#if (or system.enableMongo system.enableES)}}
data:
{{/if}}
{{#if system.enableMongo}}
mongodb:
uri: mongodb://admin:admin@127.0.0.1:27017/{{projectName}}
{{/if}}
{{#if system.enableES}}
elasticsearch:
cluster-name: es-cluster
cluster-nodes: 127.0.0.1:9300
repositories:
enabled: true
{{/if}}
datasource:
username: a_LAB01_da1af574b
password: 'D33f8870'
url: jdbc:mysql://127.0.0.1:3306/{{projectName}}?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
isSyncDBSchema: false
defaultSchema: a_LAB01_da1af574b
dynamic:
druid: #以下是全局默认值,可以全局更改
filters: stat,log4j2
#配置初始化大小/最小/最大
initial-size: 1
min-idle: 1
max-active: 20
#获取连接等待超时时间
max-wait: 60000
#间隔多久进行一次检测,检测需要关闭的空闲连接
time-between-eviction-runs-millis: 60000
#一个连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
#打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20
datasource:
master:
username: ${spring.datasource.username}
password: ${spring.datasource.password}
url: ${spring.datasource.url}
driver-class-name: ${spring.datasource.driver-class-name}
conf: classpath:liquibase/master.xml
isSyncDBSchema: ${spring.datasource.isSyncDBSchema}
defaultSchema: ${spring.datasource.defaultSchema}
db2:
username: ${spring.datasource.username}
password: ${spring.datasource.password}
url: ${spring.datasource.url}
driver-class-name: ${spring.datasource.driver-class-name}
conf: classpath:liquibase/master.xml
isSyncDBSchema: ${spring.datasource.isSyncDBSchema}
defaultSchema: ${spring.datasource.defaultSchema}
#Mybatis-plus配置
mybatis-plus:
global-config:
refresh-mapper: true
db-config:
# 全局逻辑已删除默认值
logic-delete-value: 0
# 全局逻辑未删除默认值
logic-not-delete-value: 1
mapper-locations: classpath*:/mapper/*/*/*.xml
configuration:
jdbc-type-for-null: 'null'
map-underscore-to-camel-case: false
#阿里sentinel熔断器
feign:
httpclient:
enabled: true
sentinel:
enabled: true
compression:
request:
enabled: true
mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
min-response-size: 10240
response:
enabled: true
{{#if system.enableES}}
management:
health:
elasticsearch:
enabled: false
{{/if}}
{{#if system.enableGlobalTransaction}}
seata:
enabled: true #是否开启全局事务
application-id: {{projectName}} #服务标识
tx-service-group: {{projectName}}group #事务组
service:
vgroup-mapping:
{{projectName}}group: default #指定事务组对应的Tc Server集群
registry:
type: nacos #注册中心
nacos:
application: seata-server #Tc Server服务标识
server-addr: 127.0.0.1:8848 #注册中心地址
group: DEFAULT_GROUP #服务组
namespace: #服务命名空间
userName: #用户名
password: #密码
{{/if}}
{{#if system.enableMQ}}
rocketmq:
producer:
namesrvAddr: 127.0.0.1:9876
isOnOff: 'off'
groupName: default
topic: default
consumer:
namesrvAddr: 127.0.0.1:9876
isOnOff: 'off'
groupName: default
topic: default
{{/if}}
#Log配置
logging:
level:
cn.ibizlab: debug
{{packageName}}: debug
org.springframework.boot.autoconfigure: ERROR
#zuul网关超时设置
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
#系统是否开启权限验证、是否开启缓存
#缓存级别:无缓存(无配置项)、一级缓存(L1)、二级缓存(L2)
ibiz:
systemid: {{system.codeName}}
systemname: {{system.logicName}}
enablePermissionValid: true
cacheLevel: L1 #(L1)一级本地caffeine缓存;(L2)caffeine缓存+Redis缓存
{{#eq system.saaSMode 4}}
saas:
column: SRFDCID
sys-tables: ACT_RU_TASK,act_re_procdef,databasechangelog,databasechangeloglock{{#each system.entities as |entity|}}{{#if (and (eq entity.storage "SQL") (entity.saaSMode 0))}},{{entity.tableName}}{{/if}}{{/each}}
{{/eq}}
### jobs
jobs:
#admin-address: http://127.0.0.1:40005
app-name: {{lowerCase system.name}}
app-port: 9999
#app-ip: 127.0.0.1
### 启用Gzip压缩
server:
compression:
enabled: true
mime-types: application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
min-response-size: 10240
Markdown 格式
0% or
您添加了 0 到此讨论。请谨慎行事。
先完成此消息的编辑!
想要评论请 注册