Fix 2.9.3 (#476)

* some fix

* detail AutoTransform

* fix: h5 captcha

* fix: auto-share

* fix avatar cache

* open: ref-detail, field label

* fix bool style in protable

* better : field-formula

* fix: imports owningUser

* post passwd
This commit is contained in:
RB 2022-06-30 20:41:03 +08:00 committed by GitHub
parent ac9129c036
commit 8bb2a4505b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 180 additions and 118 deletions

2
@rbv

@ -1 +1 @@
Subproject commit 277f6eb150627c53654dd02238870b7e138c2f53 Subproject commit 20c457c31c7c569bc5590d495447e62f8a1b6cb8

View file

@ -10,7 +10,7 @@
</parent> </parent>
<groupId>com.rebuild</groupId> <groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId> <artifactId>rebuild</artifactId>
<version>2.9.2</version> <version>2.9.3</version>
<name>rebuild</name> <name>rebuild</name>
<description>Building your business-systems freely!</description> <description>Building your business-systems freely!</description>
<!-- UNCOMMENT USE TOMCAT --> <!-- UNCOMMENT USE TOMCAT -->

View file

@ -65,11 +65,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/** /**
* Rebuild Version * Rebuild Version
*/ */
public static final String VER = "2.9.2"; public static final String VER = "2.9.3";
/** /**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2} * Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/ */
public static final int BUILD = 2090209; public static final int BUILD = 2090210;
static { static {
// Driver for DB // Driver for DB

View file

@ -8,6 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.configuration.general; package com.rebuild.core.configuration.general;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import org.springframework.core.NamedThreadLocal;
/** /**
* /明细实体权限处理创建明细实体必须指定主实体以便验证权限 * /明细实体权限处理创建明细实体必须指定主实体以便验证权限
@ -17,17 +18,22 @@ import cn.devezhao.persist4j.engine.ID;
*/ */
public class FormBuilderContextHolder { public class FormBuilderContextHolder {
private static final ThreadLocal<ID> MAINID_OF_DETAIL = new ThreadLocal<>(); private static final ThreadLocal<ID> MAINID_OF_DETAIL = new NamedThreadLocal<>("MainId from details");
public static void setMainIdOfDetail(ID mainId) { /**
MAINID_OF_DETAIL.set(mainId); * @param mainid
*/
public static void setMainIdOfDetail(ID mainid) {
MAINID_OF_DETAIL.set(mainid);
} }
public static ID getMainIdOfDetail() { /**
return MAINID_OF_DETAIL.get(); * @param once
} * @return
*/
public static void clear() { public static ID getMainIdOfDetail(boolean once) {
MAINID_OF_DETAIL.remove(); ID mainid = MAINID_OF_DETAIL.get();
if (mainid != null && once) MAINID_OF_DETAIL.remove();
return mainid;
} }
} }

View file

@ -110,7 +110,7 @@ public class FormsBuilder extends FormsManager {
// 新建 // 新建
if (record == null) { if (record == null) {
if (hasMainEntity != null) { if (hasMainEntity != null) {
ID mainid = FormBuilderContextHolder.getMainIdOfDetail(); ID mainid = FormBuilderContextHolder.getMainIdOfDetail(false);
Assert.notNull(mainid, "Call `FormBuilderContextHolder#setMainIdOfDetail` first!"); Assert.notNull(mainid, "Call `FormBuilderContextHolder#setMainIdOfDetail` first!");
approvalState = EntityHelper.isUnsavedId(mainid) ? null : getHadApproval(hasMainEntity, mainid); approvalState = EntityHelper.isUnsavedId(mainid) ? null : getHadApproval(hasMainEntity, mainid);
@ -237,7 +237,7 @@ public class FormsBuilder extends FormsManager {
// 明细实体 // 明细实体
ID mainid = FormBuilderContextHolder.getMainIdOfDetail(); ID mainid = FormBuilderContextHolder.getMainIdOfDetail(false);
if (mainid == null) { if (mainid == null) {
Field dtmField = MetadataHelper.getDetailToMainField(entity); Field dtmField = MetadataHelper.getDetailToMainField(entity);
Object[] o = Application.getQueryFactory().uniqueNoFilter(recordId, dtmField.getName()); Object[] o = Application.getQueryFactory().uniqueNoFilter(recordId, dtmField.getName());
@ -475,7 +475,7 @@ public class FormsBuilder extends FormsManager {
* @param user4Desensitized 不传则不脱敏 * @param user4Desensitized 不传则不脱敏
* @return * @return
* @see FieldValueHelper#wrapFieldValue(Object, EasyField) * @see FieldValueHelper#wrapFieldValue(Object, EasyField)
* @see com.rebuild.core.support.general.DataListWrapper#wrapFieldValue(Object, Field) * @see com.rebuild.core.support.general.DataListWrapper#wrapFieldValue(Object, Field, String)
*/ */
public Object wrapFieldValue(Record data, EasyField field, ID user4Desensitized) { public Object wrapFieldValue(Record data, EasyField field, ID user4Desensitized) {
Object value = data.getObjectValue(field.getName()); Object value = data.getObjectValue(field.getName());

View file

@ -37,7 +37,6 @@ import java.util.*;
public class DataImporter extends HeavyTask<Integer> { public class DataImporter extends HeavyTask<Integer> {
final private ImportRule rule; final private ImportRule rule;
private ID owningUser;
final private List<Object[]> traceLogs = new ArrayList<>(); final private List<Object[]> traceLogs = new ArrayList<>();
private String cellTraces = null; private String cellTraces = null;
@ -54,7 +53,7 @@ public class DataImporter extends HeavyTask<Integer> {
final List<Cell[]> rows = new DataFileParser(rule.getSourceFile()).parse(); final List<Cell[]> rows = new DataFileParser(rule.getSourceFile()).parse();
this.setTotal(rows.size() - 1); this.setTotal(rows.size() - 1);
owningUser = rule.getDefaultOwningUser() != null ? rule.getDefaultOwningUser() : getUser(); final ID defaultOwning = rule.getDefaultOwningUser() != null ? rule.getDefaultOwningUser() : getUser();
GeneralEntityServiceContextHolder.setSkipSeriesValue(); GeneralEntityServiceContextHolder.setSkipSeriesValue();
for (final Cell[] row : rows) { for (final Cell[] row : rows) {
@ -69,7 +68,7 @@ public class DataImporter extends HeavyTask<Integer> {
} }
try { try {
Record record = checkoutRecord(row); Record record = checkoutRecord(row, defaultOwning);
if (record == null) { if (record == null) {
traceLogs.add(new Object[] { fc.getRowNo(), "SKIP" }); traceLogs.add(new Object[] { fc.getRowNo(), "SKIP" });
} else { } else {
@ -98,11 +97,13 @@ public class DataImporter extends HeavyTask<Integer> {
} }
/** /**
*
* @param row * @param row
* @param defaultOwning
* @return * @return
*/ */
protected Record checkoutRecord(Cell[] row) { protected Record checkoutRecord(Cell[] row, ID defaultOwning) {
Record recordHub = EntityHelper.forNew(rule.getToEntity().getEntityCode(), this.owningUser); Record recordHub = EntityHelper.forNew(rule.getToEntity().getEntityCode(), defaultOwning);
// 解析数据 // 解析数据
RecordCheckout recordCheckout = new RecordCheckout(rule.getFiledsMapping()); RecordCheckout recordCheckout = new RecordCheckout(rule.getFiledsMapping());
@ -124,7 +125,7 @@ public class DataImporter extends HeavyTask<Integer> {
if (repeat != null && rule.getRepeatOpt() == ImportRule.REPEAT_OPT_UPDATE) { if (repeat != null && rule.getRepeatOpt() == ImportRule.REPEAT_OPT_UPDATE) {
// 更新 // 更新
checkout = EntityHelper.forUpdate(repeat, this.owningUser); checkout = EntityHelper.forUpdate(repeat, defaultOwning);
for (Iterator<String> iter = recordHub.getAvailableFieldIterator(); iter.hasNext(); ) { for (Iterator<String> iter = recordHub.getAvailableFieldIterator(); iter.hasNext(); ) {
String field = iter.next(); String field = iter.next();
if (MetadataHelper.isCommonsField(field)) continue; if (MetadataHelper.isCommonsField(field)) continue;

View file

@ -27,6 +27,7 @@ import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataSorter; import com.rebuild.core.metadata.MetadataSorter;
import com.rebuild.core.metadata.easymeta.*; import com.rebuild.core.metadata.easymeta.*;
import com.rebuild.core.metadata.impl.MetadataModificationException; import com.rebuild.core.metadata.impl.MetadataModificationException;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.state.StateManager; import com.rebuild.core.support.state.StateManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -72,7 +73,20 @@ public class RecordCheckout {
Object value = checkoutFieldValue(field, cellValue, true); Object value = checkoutFieldValue(field, cellValue, true);
if (value != null) { if (value != null) {
record.setObjectValue(field.getName(), value); if (field.getName().equalsIgnoreCase(EntityHelper.OwningUser)) {
User owning = Application.getUserStore().getUser((ID) value);
if (owning.getOwningDept() == null) {
putTraceLog(cellValue, Language.L(EasyMetaFactory.getDisplayType(field)));
} else {
// 用户部门联动
record.setObjectValue(field.getName(), value);
record.setID(EntityHelper.OwningDept, (ID) owning.getOwningDept().getIdentity());
}
} else {
record.setObjectValue(field.getName(), value);
}
} else { } else {
putTraceLog(cellValue, Language.L(EasyMetaFactory.getDisplayType(field))); putTraceLog(cellValue, Language.L(EasyMetaFactory.getDisplayType(field)));
} }
@ -182,7 +196,7 @@ public class RecordCheckout {
final String val = cell.asString(); final String val = cell.asString();
final Entity refEntity = field.getReferenceEntity(); final Entity refEntity = field.getReferenceEntity();
// 支持ID // 支持 ID
if (ID.isId(val) && ID.valueOf(val).getEntityCode().intValue() == refEntity.getEntityCode()) { if (ID.isId(val) && ID.valueOf(val).getEntityCode().intValue() == refEntity.getEntityCode()) {
ID checkId = ID.valueOf(val); ID checkId = ID.valueOf(val);
Object exists = Application.getQueryFactory().uniqueNoFilter(checkId, refEntity.getPrimaryField().getName()); Object exists = Application.getQueryFactory().uniqueNoFilter(checkId, refEntity.getPrimaryField().getName());

View file

@ -274,12 +274,15 @@ public class GeneralEntityService extends ObservableService implements EntitySer
final ID currentUser = UserContextHolder.getUser(); final ID currentUser = UserContextHolder.getUser();
final String entityName = MetadataHelper.getEntityName(record); final String entityName = MetadataHelper.getEntityName(record);
// 如用户无更新权限则降级为只读共享 boolean fromTriggerNoDowngrade = GeneralEntityServiceContextHolder.isFromTrigger(false);
if ((rights & BizzPermission.UPDATE.getMask()) != 0) { if (!fromTriggerNoDowngrade) {
if (!Application.getPrivilegesManager().allowUpdate(to, record.getEntityCode()) /* 目标用户无基础更新权限 */ // 如用户无更新权限则降级为只读共享
|| !Application.getPrivilegesManager().allow(currentUser, record, BizzPermission.UPDATE, true) /* 操作用户无记录更新权限 */) { if ((rights & BizzPermission.UPDATE.getMask()) != 0) {
rights = BizzPermission.READ.getMask(); if (!Application.getPrivilegesManager().allowUpdate(to, record.getEntityCode()) /* 目标用户无基础更新权限 */
log.warn("Downgrade share rights to READ(8) : {}", record); || !Application.getPrivilegesManager().allow(currentUser, record, BizzPermission.UPDATE, true) /* 操作用户无记录更新权限 */) {
rights = BizzPermission.READ.getMask();
log.warn("Downgrade share rights to READ(8) : {}", record);
}
} }
} }
@ -311,10 +314,12 @@ public class GeneralEntityService extends ObservableService implements EntitySer
log.debug("The record has been shared and has the same rights, ignore : {}", record); log.debug("The record has been shared and has the same rights, ignore : {}", record);
} }
// 可以共享给自己
// } else if (to.equals(Application.getRecordOwningCache().getOwningUser(record))) {
// log.debug("Share to the same user as the record, ignore : {}", record);
} else { } else {
// // 可以共享给自己
// if (to.equals(Application.getRecordOwningCache().getOwningUser(record))) {
// log.debug("Share to the same user as the record, ignore : {}", record);
// }
delegateService.create(sharedAfter); delegateService.create(sharedAfter);
affected = 1; affected = 1;
shareChange = true; shareChange = true;
@ -391,7 +396,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
return Collections.emptyMap(); return Collections.emptyMap();
} }
final boolean fromTriggerNoFilter = GeneralEntityServiceContextHolder.isFromTriggersOnce(); final boolean fromTriggerNoFilter = GeneralEntityServiceContextHolder.isFromTrigger(false);
Map<String, Set<ID>> entityRecordsMap = new HashMap<>(); Map<String, Set<ID>> entityRecordsMap = new HashMap<>();
Entity mainEntity = MetadataHelper.getEntity(recordMain.getEntityCode()); Entity mainEntity = MetadataHelper.getEntity(recordMain.getEntityCode());

View file

@ -66,17 +66,17 @@ public class GeneralEntityServiceContextHolder {
* *
* @param recordId * @param recordId
*/ */
public static void setFromTriggers(ID recordId) { public static void setFromTrigger(ID recordId) {
FROM_TRIGGERS.set(recordId); FROM_TRIGGERS.set(recordId);
} }
/** /**
* @return * @return
* @see #setFromTriggers(ID) * @see #setFromTrigger(ID)
*/ */
public static boolean isFromTriggersOnce() { public static boolean isFromTrigger(boolean once) {
ID recordId = FROM_TRIGGERS.get(); ID recordId = FROM_TRIGGERS.get();
if (recordId != null) FROM_TRIGGERS.remove(); if (recordId != null && once) FROM_TRIGGERS.remove();
return recordId != null; return recordId != null;
} }

View file

@ -147,7 +147,7 @@ public class RecordTransfomer extends SetUser {
int fillbackMode = transConfig.getIntValue("fillbackMode"); int fillbackMode = transConfig.getIntValue("fillbackMode");
// 仅更新无业务规则 // 仅更新无业务规则
if (fillbackMode == 3) { if (fillbackMode == 3 || fillbackMode == 0) {
Application.getCommonsService().update(updateSource, false); Application.getCommonsService().update(updateSource, false);
} }
// 忽略审批状态进行中强制更新 // 忽略审批状态进行中强制更新

View file

@ -8,6 +8,8 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.trigger.impl; package com.rebuild.core.service.trigger.impl;
import cn.devezhao.persist4j.Entity; import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.metadata.MissingMetaExcetion; import cn.devezhao.persist4j.metadata.MissingMetaExcetion;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application; import com.rebuild.core.Application;
@ -80,12 +82,20 @@ public class AggregationEvaluator {
Set<String> matchsVars = ContentWithFieldVars.matchsVars(formula); Set<String> matchsVars = ContentWithFieldVars.matchsVars(formula);
List<String[]> fields = new ArrayList<>(); List<String[]> fields = new ArrayList<>();
Set<String> nonNumericFields = new HashSet<>();
for (String m : matchsVars) { for (String m : matchsVars) {
String[] fieldAndFunc = m.split(MetadataHelper.SPLITER_RE); String[] fieldAndFunc = m.split(MetadataHelper.SPLITER_RE);
if (MetadataHelper.getLastJoinField(sourceEntity, fieldAndFunc[0]) == null) { Field field;
if ((field = MetadataHelper.getLastJoinField(sourceEntity, fieldAndFunc[0])) == null) {
throw new MissingMetaExcetion(fieldAndFunc[0], sourceEntity.getName()); throw new MissingMetaExcetion(fieldAndFunc[0], sourceEntity.getName());
} }
fields.add(fieldAndFunc); fields.add(fieldAndFunc);
// 数字型
if (fieldAndFunc.length > 1 || field.getType() == FieldType.LONG || field.getType() == FieldType.DECIMAL);
else {
nonNumericFields.add("nn:" + fieldAndFunc[0]);
}
} }
if (fields.isEmpty()) { if (fields.isEmpty()) {
log.warn("No fields found in formula : {}", formula); log.warn("No fields found in formula : {}", formula);
@ -134,8 +144,9 @@ public class AggregationEvaluator {
} else { } else {
continue; continue;
} }
Object value = useSourceData[i] == null ? 0 : useSourceData[i]; Object value = useSourceData[i];
if (value == null) value = nonNumericFields.contains("nn:" + field[0]) ? "" : 0;
envMap.put(fieldKey, value); envMap.put(fieldKey, value);
} }

View file

@ -98,7 +98,7 @@ public class AutoAssign extends TriggerAction {
} }
PrivilegesGuardContextHolder.setSkipGuard(recordId); PrivilegesGuardContextHolder.setSkipGuard(recordId);
GeneralEntityServiceContextHolder.setFromTriggers(recordId); GeneralEntityServiceContextHolder.setFromTrigger(recordId);
try { try {
Application.getEntityService(actionContext.getSourceEntity().getEntityCode()) Application.getEntityService(actionContext.getSourceEntity().getEntityCode())
@ -111,7 +111,7 @@ public class AutoAssign extends TriggerAction {
} finally { } finally {
PrivilegesGuardContextHolder.getSkipGuardOnce(); PrivilegesGuardContextHolder.getSkipGuardOnce();
GeneralEntityServiceContextHolder.isFromTriggersOnce(); GeneralEntityServiceContextHolder.isFromTrigger(true);
} }
} }
} }

View file

@ -66,7 +66,7 @@ public class AutoShare extends AutoAssign {
final EntityService es = Application.getEntityService(actionContext.getSourceEntity().getEntityCode()); final EntityService es = Application.getEntityService(actionContext.getSourceEntity().getEntityCode());
for (ID toUser : toUsers) { for (ID toUser : toUsers) {
PrivilegesGuardContextHolder.setSkipGuard(recordId); PrivilegesGuardContextHolder.setSkipGuard(recordId);
GeneralEntityServiceContextHolder.setFromTriggers(recordId); GeneralEntityServiceContextHolder.setFromTrigger(recordId);
try { try {
es.share(recordId, toUser, cascades, shareRights); es.share(recordId, toUser, cascades, shareRights);

View file

@ -32,7 +32,7 @@ public class SystemDiagnosis {
public static final String DatabaseBackupFail = "DatabaseBackupFail"; public static final String DatabaseBackupFail = "DatabaseBackupFail";
public static final String DataFileBackupFail = "DataFileBackupFail"; public static final String DataFileBackupFail = "DataFileBackupFail";
public static String _DENIEDMSG = null; volatile public static String _DENIEDMSG = null;
public void diagnose() { public void diagnose() {
ServerStatus.getLastStatus(true); ServerStatus.getLastStatus(true);
@ -58,12 +58,12 @@ public class SystemDiagnosis {
if (usersMsg == null) dangers.remove(UsersMsg); if (usersMsg == null) dangers.remove(UsersMsg);
else dangers.put(UsersMsg, usersMsg); else dangers.put(UsersMsg, usersMsg);
// MULTIPLE RUNNING INSTANCES DETECTED!
_DENIEDMSG = echoValidity.getString("deniedMsg"); _DENIEDMSG = echoValidity.getString("deniedMsg");
} else { } else {
dangers.remove(AdminMsg); dangers.remove(AdminMsg);
dangers.remove(UsersMsg); dangers.remove(UsersMsg);
_DENIEDMSG = null;
} }
Application.getCommonsCache().putx(CKEY_DANGERS, dangers, CommonsCache.TS_DAY); Application.getCommonsCache().putx(CKEY_DANGERS, dangers, CommonsCache.TS_DAY);

View file

@ -9,8 +9,8 @@ package com.rebuild.web.admin.metadata;
import cn.devezhao.commons.web.ServletUtils; import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Entity; import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Record; import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
@ -23,7 +23,6 @@ import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.EasyField; import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.UserHelper; import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.UserService;
import com.rebuild.web.BaseController; import com.rebuild.web.BaseController;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -63,6 +62,7 @@ public class FormDesignController extends BaseController {
@RequestMapping({"form-update"}) @RequestMapping({"form-update"})
public void sets(@PathVariable String entity, public void sets(@PathVariable String entity,
HttpServletRequest request, HttpServletResponse response) { HttpServletRequest request, HttpServletResponse response) {
final ID user = getRequestUser(request);
JSON formJson = ServletUtils.getRequestJson(request); JSON formJson = ServletUtils.getRequestJson(request);
// 修改字段名称 // 修改字段名称
@ -88,13 +88,10 @@ public class FormDesignController extends BaseController {
List<Record> willUpdate = new ArrayList<>(); List<Record> willUpdate = new ArrayList<>();
Entity entityMeta = MetadataHelper.getEntity(entity); Entity entityMeta = MetadataHelper.getEntity(entity);
for (Map.Entry<String, String> e : newLabels.entrySet()) { for (Map.Entry<String, String> e : newLabels.entrySet()) {
Field fieldMeta = entityMeta.getField(e.getKey()); EasyField fieldEasy = EasyMetaFactory.valueOf(entityMeta.getField(e.getKey()));
EasyField fieldEasy = EasyMetaFactory.valueOf(fieldMeta); if (fieldEasy.getMetaId() == null) continue;
if (fieldEasy.isBuiltin() || fieldEasy.getMetaId() == null) {
continue;
}
Record fieldRecord = EntityHelper.forUpdate(fieldEasy.getMetaId(), UserService.SYSTEM_USER, false); Record fieldRecord = EntityHelper.forUpdate(fieldEasy.getMetaId(), user, false);
fieldRecord.setString("fieldLabel", e.getValue()); fieldRecord.setString("fieldLabel", e.getValue());
willUpdate.add(fieldRecord); willUpdate.add(fieldRecord);
} }

View file

@ -8,6 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.commons; package com.rebuild.web.commons;
import cn.devezhao.commons.CodecUtils; import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.commons.web.ServletUtils; import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.rebuild.api.user.AuthTokenManager; import com.rebuild.api.user.AuthTokenManager;
@ -35,8 +36,12 @@ import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.*; import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
/** /**
* 文件下载/查看 * 文件下载/查看
@ -100,8 +105,8 @@ public class FileDownloader extends BaseController {
BufferedImage bi = ImageIO.read(img); BufferedImage bi = ImageIO.read(img);
if (bi == null) { if (bi == null) {
log.debug("None image type : {}", filePath); log.debug("Unsupport image type : {}", filePath);
writeStream(new FileInputStream(img), response); writeStream(Files.newInputStream(img.toPath()), response);
return; return;
} }
@ -146,10 +151,10 @@ public class FileDownloader extends BaseController {
private int parseWidth(String imageView2) { private int parseWidth(String imageView2) {
if (!imageView2.contains("/w/")) { if (!imageView2.contains("/w/")) {
return 1000; return 1000;
} else {
String w = imageView2.split("/w/")[1].split("/")[0];
return ObjectUtils.toInt(w, 1000);
} }
String w = imageView2.split("/w/")[1].split("/")[0];
return Integer.parseInt(w);
} }
@GetMapping(value = {"download/**", "access/**"}) @GetMapping(value = {"download/**", "access/**"})
@ -240,7 +245,7 @@ public class FileDownloader extends BaseController {
long size = FileUtils.sizeOf(file); long size = FileUtils.sizeOf(file);
response.setHeader("Content-Length", String.valueOf(size)); response.setHeader("Content-Length", String.valueOf(size));
try (InputStream fis = new FileInputStream(file)) { try (InputStream fis = Files.newInputStream(file.toPath())) {
return writeStream(fis, response); return writeStream(fis, response);
} }
} }
@ -319,4 +324,5 @@ public class FileDownloader extends BaseController {
if (attname != null) url += "&attname=" + CodecUtils.urlEncode(attname); if (attname != null) url += "&attname=" + CodecUtils.urlEncode(attname);
resp.sendRedirect(AppUtils.getContextPath(url)); resp.sendRedirect(AppUtils.getContextPath(url));
} }
} }

View file

@ -131,7 +131,7 @@ public class GeneralModelController extends EntityController {
return model; return model;
} finally { } finally {
FormBuilderContextHolder.clear(); FormBuilderContextHolder.getMainIdOfDetail(true);
} }
} }

View file

@ -45,6 +45,8 @@ import java.io.IOException;
@RequestMapping("/account") @RequestMapping("/account")
public class UserAvatar extends BaseController { public class UserAvatar extends BaseController {
public static final String SK_DAVATAR = "davatarTime";
@GetMapping("/user-avatar") @GetMapping("/user-avatar")
public void renderAvatat(HttpServletRequest request, HttpServletResponse response) throws IOException { public void renderAvatat(HttpServletRequest request, HttpServletResponse response) throws IOException {
renderUserAvatar(getRequestUser(request), request, response); renderUserAvatar(getRequestUser(request), request, response);
@ -137,7 +139,7 @@ public class UserAvatar extends BaseController {
record.setString("avatarUrl", uploadName); record.setString("avatarUrl", uploadName);
Application.getBean(UserService.class).update(record); Application.getBean(UserService.class).update(record);
ServletUtils.setSessionAttribute(request, "davatarTime", System.currentTimeMillis()); ServletUtils.setSessionAttribute(request, SK_DAVATAR, System.currentTimeMillis());
return RespBody.ok(uploadName); return RespBody.ok(uploadName);
} }

View file

@ -8,6 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.user; package com.rebuild.web.user;
import cn.devezhao.commons.EncryptUtils; import cn.devezhao.commons.EncryptUtils;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Record; import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
@ -113,15 +114,15 @@ public class UserSettingsController extends EntityController {
return RespBody.ok(); return RespBody.ok();
} }
@RequestMapping("/user/save-passwd") @PostMapping("/user/save-passwd")
public RespBody savePasswd(HttpServletRequest request) { public RespBody savePasswd(HttpServletRequest request) {
final ID user = getRequestUser(request); final ID user = getRequestUser(request);
String oldp = getParameterNotNull(request, "oldp");
String newp = getParameterNotNull(request, "newp");
Object[] o = Application.createQuery("select password from User where userId = ?") JSONObject p = (JSONObject) ServletUtils.getRequestJson(request);
.setParameter(1, user) String oldp = p.getString("oldp");
.unique(); String newp = p.getString("newp");
Object[] o = Application.getQueryFactory().uniqueNoFilter(user, "password");
if (o == null || !StringUtils.equals((String) o[0], EncryptUtils.toSHA256Hex(oldp))) { if (o == null || !StringUtils.equals((String) o[0], EncryptUtils.toSHA256Hex(oldp))) {
return RespBody.errorl("原密码输入有误"); return RespBody.errorl("原密码输入有误");
} }

View file

@ -30,6 +30,7 @@ import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.integration.SMSender; import com.rebuild.core.support.integration.SMSender;
import com.rebuild.utils.AES; import com.rebuild.utils.AES;
import com.rebuild.utils.AppUtils; import com.rebuild.utils.AppUtils;
import com.rebuild.web.user.UserAvatar;
import com.wf.captcha.utils.CaptchaUtil; import com.wf.captcha.utils.CaptchaUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -157,9 +158,13 @@ public class LoginController extends LoginAction {
public RespBody userLogin(HttpServletRequest request, HttpServletResponse response) { public RespBody userLogin(HttpServletRequest request, HttpServletResponse response) {
String vcode = getParameter(request, "vcode"); String vcode = getParameter(request, "vcode");
Boolean needVcode = (Boolean) ServletUtils.getSessionAttribute(request, SK_NEED_VCODE); Boolean needVcode = (Boolean) ServletUtils.getSessionAttribute(request, SK_NEED_VCODE);
if (needVcode != null && needVcode if ((needVcode != null && needVcode) || StringUtils.isNotBlank(vcode)) {
&& (StringUtils.isBlank(vcode) || !CaptchaUtil.ver(vcode, request))) { if (StringUtils.isBlank(vcode)) {
return RespBody.errorl("验证码错误"); ServletUtils.setSessionAttribute(request, SK_NEED_VCODE, true);
return RespBody.error("VCODE");
} else if (!CaptchaUtil.ver(vcode, request)) {
return RespBody.errorl("验证码错误");
}
} }
final String user = getParameterNotNull(request, "user"); final String user = getParameterNotNull(request, "user");
@ -176,11 +181,14 @@ public class LoginController extends LoginAction {
return RespBody.error(hasError); return RespBody.error(hasError);
} }
// 清理 // 清理验证码
getLoginRetryTimes(user, -1); getLoginRetryTimes(user, -1);
ServletUtils.setSessionAttribute(request, SK_NEED_VCODE, null); ServletUtils.setSessionAttribute(request, SK_NEED_VCODE, null);
// 头像缓存
ServletUtils.setSessionAttribute(request, UserAvatar.SK_DAVATAR, System.currentTimeMillis());
final User loginUser = Application.getUserStore().getUser(user); final User loginUser = Application.getUserStore().getUser(user);
final boolean isMobile = AppUtils.isRbMobile(request);
Map<String, Object> resMap = new HashMap<>(); Map<String, Object> resMap = new HashMap<>();
@ -193,14 +201,14 @@ public class LoginController extends LoginAction {
Application.getCommonsCache().putx("2FA" + userToken, loginUser.getId(), CommonsCache.TS_HOUR / 4); // 15m Application.getCommonsCache().putx("2FA" + userToken, loginUser.getId(), CommonsCache.TS_HOUR / 4); // 15m
resMap.put("login2FaUserToken", userToken); resMap.put("login2FaUserToken", userToken);
if (AppUtils.isRbMobile(request)) { if (isMobile) {
request.getSession().invalidate(); request.getSession().invalidate();
} }
return RespBody.ok(resMap); return RespBody.ok(resMap);
} }
if (AppUtils.isRbMobile(request)) { if (isMobile) {
resMap = loginSuccessedH5(request, response, loginUser.getId()); resMap = loginSuccessedH5(request, response, loginUser.getId());
} else { } else {
Integer ed = loginSuccessed( Integer ed = loginSuccessed(

View file

@ -24,12 +24,13 @@
font-size: 32px; font-size: 32px;
color: #4285f4; color: #4285f4;
} }
.card.entity .badge { .card.entity i.badge {
position: absolute; position: absolute;
top: 11px; top: 11px;
right: 11px; right: 11px;
font-size: 11px;
text-transform: uppercase; text-transform: uppercase;
font-style: normal;
padding-bottom: 0;
} }
.card.entity span { .card.entity span {
margin-top: 2px; margin-top: 2px;
@ -156,8 +157,8 @@
$t.find('.icon').addClass(`zmdi-${item.icon}`) $t.find('.icon').addClass(`zmdi-${item.icon}`)
$t.find('span').text(item.entityLabel) $t.find('span').text(item.entityLabel)
$t.find('p').text(item.comments || '-') $t.find('p').text(item.comments || '-')
if (item.builtin) $(`<i class="badge badge-pill badge-secondary thin text-muted">${$L('内置')}</i>`).appendTo($t.find('a.card')) if (item.builtin) $(`<i class="badge badge-pill badge-secondary font-weight-light">${$L('内置')}</i>`).appendTo($t.find('a.card'))
if (!!item.detailEntity) $(`<i class="badge badge-pill badge-secondary thin text-muted">${$L('明细')}</i>`).appendTo($t.find('a.card')) if (!!item.detailEntity) $(`<i class="badge badge-pill badge-secondary font-weight-light">${$L('明细')}</i>`).appendTo($t.find('a.card'))
return $t return $t
} }
</script> </script>

View file

@ -349,7 +349,7 @@
<div class="dropdown-menu common-patt"> <div class="dropdown-menu common-patt">
<h5>[[${bundle.L('常用')}]]</h5> <h5>[[${bundle.L('常用')}]]</h5>
<a class="badge" data-patt="^([0-9A-Z]{15}|[0-9A-Z]{17}|[0-9A-Z]{18}|[0-9A-Z]{20})$">[[${bundle.L('税号')}]]</a> <a class="badge" data-patt="^([0-9A-Z]{15}|[0-9A-Z]{17}|[0-9A-Z]{18}|[0-9A-Z]{20})$">[[${bundle.L('税号')}]]</a>
<a class="badge" data-patt="^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9X]$">[[${bundle.L('身份证')}]]</a> <a class="badge" data-patt="^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$">[[${bundle.L('身份证')}]]</a>
<a class="badge" data-patt="^[1-9][0-9]{4,10}$">[[${bundle.L('QQ号')}]]</a> <a class="badge" data-patt="^[1-9][0-9]{4,10}$">[[${bundle.L('QQ号')}]]</a>
<a class="badge" data-patt="^[1-9][0-9]{5}$">[[${bundle.L('邮编')}]]</a> <a class="badge" data-patt="^[1-9][0-9]{5}$">[[${bundle.L('邮编')}]]</a>
<a class="badge" data-patt="^[\u4e00-\u9fa5]{0,}$">[[${bundle.L('仅中文')}]]</a> <a class="badge" data-patt="^[\u4e00-\u9fa5]{0,}$">[[${bundle.L('仅中文')}]]</a>

View file

@ -125,7 +125,7 @@
const $name = $(`<td><a href="field/${item.fieldName}" class="column-main">${item.fieldLabel}</a></td>`).appendTo($tr) const $name = $(`<td><a href="field/${item.fieldName}" class="column-main">${item.fieldLabel}</a></td>`).appendTo($tr)
if (item.fieldName === wpc.nameField) { if (item.fieldName === wpc.nameField) {
$tr.addClass('primary') $tr.addClass('primary')
$(`<span class="badge badge-pill badge-secondary thin ml-1">${$L('名称')}</span>`).appendTo($name) $(`<span class="badge badge-pill badge-secondary font-weight-light ml-1 pb-0">${$L('名称')}</span>`).appendTo($name)
} else if (!item.creatable) { } else if (!item.creatable) {
$tr.addClass('muted') $tr.addClass('muted')
} else if (!item.nullable) { } else if (!item.nullable) {

View file

@ -514,12 +514,6 @@ a.btn {
margin-top: 2px; margin-top: 2px;
} }
.badge.thin {
font-weight: normal;
font-style: normal;
line-height: 1.45;
}
.badge + .badge { .badge + .badge {
margin-left: 3px; margin-left: 3px;
} }
@ -4606,6 +4600,14 @@ pre.unstyle {
margin-top: 4px; margin-top: 4px;
} }
.protable .table tr td .custom-control {
padding-left: 2.1377rem;
}
.protable .table tr td .custom-control + .custom-control {
margin-left: 1.385rem;
}
.protable .table { .protable .table {
border-bottom: 1px solid #dee2e6; border-bottom: 1px solid #dee2e6;
margin: 0; margin: 0;

View file

@ -207,7 +207,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
} }
.formula-calc .numbers li > a:active { .formula-calc .numbers li > a:active {
box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.06); box-shadow: inset 3px 3px 3px rgba(0, 0, 0, 0.06);
} }
.formula-calc ul li.empty-1 > a { .formula-calc ul li.empty-1 > a {

View file

@ -31,7 +31,7 @@ $(document).ready(function () {
const $copy = $('.btn-primary.copy').on('click', () => { const $copy = $('.btn-primary.copy').on('click', () => {
const sourceEntity = $val('#copySourceEntity') const sourceEntity = $val('#copySourceEntity')
if (!sourceEntity) RbHighbar.create($L('请选择从哪个实体复制')) if (!sourceEntity) return RbHighbar.create($L('请选择从哪个实体复制'))
const entityLabel = $val('#newEntityLabel') const entityLabel = $val('#newEntityLabel')
if (!entityLabel) return RbHighbar.create($L('请输入实体名称')) if (!entityLabel) return RbHighbar.create($L('请输入实体名称'))
@ -76,6 +76,8 @@ $(document).ready(function () {
parent.RbModal.resize() parent.RbModal.resize()
}) })
.trigger('change') .trigger('change')
if (e.length === 0) $(`<option value="">${$L('无可用实体')}</option>`).appendTo('#copySourceEntity')
}) })
$('#isDetail').on('click', function () { $('#isDetail').on('click', function () {

View file

@ -11,16 +11,22 @@ function verifyFormula(formula, entity, onConfirm) {
if (res.error_code === 0) { if (res.error_code === 0) {
onConfirm() onConfirm()
} else { } else {
RbAlert.create($L('计算公式可能存在错误这会导致触发器执行失败是否继续'), { RbAlert.create(
type: 'warning', <RF>
onConfirm: function () { <p>{$L('计算公式可能存在错误这会导致触发器执行失败是否继续')}</p>
this.hide() {res.error_msg && <pre className="text-danger">{res.error_msg}</pre>}
onConfirm() </RF>,
}, {
onCancel: function () { type: 'warning',
this.hide() onConfirm: function () {
}, this.hide()
}) onConfirm()
},
onCancel: function () {
this.hide()
},
}
)
} }
}) })
} }

View file

@ -80,8 +80,7 @@ $(document).ready(function () {
// _data.push({ entityName: 'Role', entityLabel: $L('角色') }) // _data.push({ entityName: 'Role', entityLabel: $L('角色') })
$(d).each(function () { $(d).each(function () {
// 明细实体默认隐藏 $(`<option value="${this.entityName}">${this.entityLabel}</option>`).appendTo('#refEntity')
$(`<option value="${this.entityName}" class="${this.mainEntity ? 'bosskey-show' : ''}">${this.entityLabel}${this.mainEntity ? ` (${$L('明细实体')})` : ''}</option>`).appendTo('#refEntity')
}) })
}) })
} }

View file

@ -1074,7 +1074,7 @@ class RbFormTime extends RbFormDateTime {
class RbFormImage extends RbFormElement { class RbFormImage extends RbFormElement {
constructor(props) { constructor(props) {
super(props) super(props)
this._inputid = `${props.field}-input${$random()}` this._htmlid = `${props.field}-${$random()}-input`
if (props.value) this.state.value = [...props.value] // clone if (props.value) this.state.value = [...props.value] // clone
if (this.props.uploadNumber) { if (this.props.uploadNumber) {
@ -1111,8 +1111,8 @@ class RbFormImage extends RbFormElement {
) )
})} })}
<span title={$L('上传图片需要 %s ', `${this.__minUpload}~${this.__maxUpload}`)} className={showUpload ? '' : 'hide'}> <span title={$L('上传图片需要 %s ', `${this.__minUpload}~${this.__maxUpload}`)} className={showUpload ? '' : 'hide'}>
<input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={this._inputid} accept="image/*" /> <input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={this._htmlid} accept="image/*" />
<label htmlFor={this._inputid} className="img-thumbnail img-upload"> <label htmlFor={this._htmlid} className="img-thumbnail img-upload">
<span className="zmdi zmdi-image-alt" /> <span className="zmdi zmdi-image-alt" />
</label> </label>
</span> </span>
@ -1221,8 +1221,8 @@ class RbFormFile extends RbFormImage {
) )
})} })}
<div className={`file-select ${showUpload ? '' : 'hide'}`}> <div className={`file-select ${showUpload ? '' : 'hide'}`}>
<input type="file" className="inputfile" ref={(c) => (this._fieldValue__input = c)} id={this._inputid} /> <input type="file" className="inputfile" ref={(c) => (this._fieldValue__input = c)} id={this._htmlid} />
<label htmlFor={this._inputid} title={$L('上传文件需要 %d ', `${this.__minUpload}~${this.__maxUpload}`)} className="btn-secondary"> <label htmlFor={this._htmlid} title={$L('上传文件需要 %d ', `${this.__minUpload}~${this.__maxUpload}`)} className="btn-secondary">
<i className="zmdi zmdi-upload" /> <i className="zmdi zmdi-upload" />
<span>{$L('上传文件')}</span> <span>{$L('上传文件')}</span>
</label> </label>
@ -1749,6 +1749,7 @@ class RbFormBool extends RbFormElement {
constructor(props) { constructor(props) {
super(props) super(props)
this._htmlid = `${props.field}-${$random()}_`
} }
renderElement() { renderElement() {
@ -1757,7 +1758,7 @@ class RbFormBool extends RbFormElement {
<label className="custom-control custom-radio custom-control-inline"> <label className="custom-control custom-radio custom-control-inline">
<input <input
className="custom-control-input" className="custom-control-input"
name={`radio-${this.props.field}`} name={`${this._htmlid}T`}
type="radio" type="radio"
checked={$isTrue(this.state.value)} checked={$isTrue(this.state.value)}
data-value="T" data-value="T"
@ -1769,7 +1770,7 @@ class RbFormBool extends RbFormElement {
<label className="custom-control custom-radio custom-control-inline"> <label className="custom-control custom-radio custom-control-inline">
<input <input
className="custom-control-input" className="custom-control-input"
name={`radio-${this.props.field}`} name={`${this._htmlid}F`}
type="radio" type="radio"
checked={!$isTrue(this.state.value)} checked={!$isTrue(this.state.value)}
data-value="F" data-value="F"
@ -1831,15 +1832,15 @@ class RbFormBarcode extends RbFormElement {
class RbFormAvatar extends RbFormElement { class RbFormAvatar extends RbFormElement {
constructor(props) { constructor(props) {
super(props) super(props)
this._inputid = `${props.field}-input${$random()}` this._htmlid = `${props.field}-${$random()}-input`
} }
renderElement() { renderElement() {
return ( return (
<div className="img-field avatar"> <div className="img-field avatar">
<span title={this.props.readonly ? null : $L('选择头像')}> <span title={this.props.readonly ? null : $L('选择头像')}>
{!this.props.readonly && <input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={this._inputid} accept="image/*" />} {!this.props.readonly && <input ref={(c) => (this._fieldValue__input = c)} type="file" className="inputfile" id={this._htmlid} accept="image/*" />}
<label htmlFor={this._inputid} className="img-thumbnail img-upload" disabled={this.props.readonly}> <label htmlFor={this._htmlid} className="img-thumbnail img-upload" disabled={this.props.readonly}>
<img src={this._formatUrl(this.state.value)} alt="Avatar" /> <img src={this._formatUrl(this.state.value)} alt="Avatar" />
</label> </label>
</span> </span>

View file

@ -106,19 +106,19 @@ class DlgChangePasswd extends RbFormHandler {
<div className="form-group row"> <div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('原密码')}</label> <label className="col-sm-3 col-form-label text-sm-right">{$L('原密码')}</label>
<div className="col-sm-7"> <div className="col-sm-7">
<input type="password" className="form-control form-control-sm" data-id="oldPasswd" onChange={this.handleChange} /> <input type="password" className="form-control form-control-sm" data-id="oldPasswd" onChange={this.handleChange} autoComplete="new-password" />
</div> </div>
</div> </div>
<div className="form-group row"> <div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('新密码')}</label> <label className="col-sm-3 col-form-label text-sm-right">{$L('新密码')}</label>
<div className="col-sm-7"> <div className="col-sm-7">
<input type="password" className="form-control form-control-sm" data-id="newPasswd" onChange={this.handleChange} /> <input type="password" className="form-control form-control-sm" data-id="newPasswd" onChange={this.handleChange} autoComplete="new-password" />
</div> </div>
</div> </div>
<div className="form-group row"> <div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('重复新密码')}</label> <label className="col-sm-3 col-form-label text-sm-right">{$L('重复新密码')}</label>
<div className="col-sm-7"> <div className="col-sm-7">
<input type="password" className="form-control form-control-sm" data-id="newPasswdAgain" onChange={this.handleChange} /> <input type="password" className="form-control form-control-sm" data-id="newPasswdAgain" onChange={this.handleChange} autoComplete="new-password" />
</div> </div>
</div> </div>
<div className="form-group row footer"> <div className="form-group row footer">
@ -142,9 +142,9 @@ class DlgChangePasswd extends RbFormHandler {
if (!s.newPasswd) return RbHighbar.create($L('请输入新密码')) if (!s.newPasswd) return RbHighbar.create($L('请输入新密码'))
if (s.newPasswd !== s.newPasswdAgain) return RbHighbar.create($L('两次输入的新密码不一致')) if (s.newPasswd !== s.newPasswdAgain) return RbHighbar.create($L('两次输入的新密码不一致'))
const $btns = $(this.refs['btns']).find('.btn').button('loading') const $btn = $(this.refs['btns']).find('.btn').button('loading')
$.post(`/settings/user/save-passwd?oldp=${$encode(s.oldPasswd)}&newp=${$encode(s.newPasswd)}`, (res) => { $.post('/settings/user/save-passwd', JSON.stringify({ oldp: s.oldPasswd, newp: s.newPasswd }), (res) => {
$btns.button('reset') $btn.button('reset')
if (res.error_code === 0) { if (res.error_code === 0) {
this.hide() this.hide()
RbHighbar.success($L('修改成功')) RbHighbar.success($L('修改成功'))