Merge branch 'master' into develop

This commit is contained in:
devezhao 2025-04-16 13:27:03 +08:00
commit 112470a4ea
33 changed files with 370 additions and 189 deletions

View file

@ -4,7 +4,7 @@ RUN apk add ttf-dejavu
RUN apk add wget
RUN mkdir -p /app/rebuild/.rebuild/
ADD https://www.qn-cdn.getrebuild.com/pub/deploy/rebuild-boot-4.0.0-beta3.jar /app/rebuild/rebuild-boot.jar
ADD ./rebuild-boot-4.0.0.jar /app/rebuild/rebuild-boot.jar
ADD https://www.qn-cdn.getrebuild.com/pub/deploy/SourceHanSansK-Regular.ttf /app/rebuild/.rebuild/
EXPOSE 18080

View file

@ -4,11 +4,6 @@ services:
environment:
MYSQL_ROOT_HOST: "%"
MYSQL_ROOT_PASSWORD: "rebuildP4wd"
MYSQL_DATABASE: rebuild40
command: sh -c "
curl -sSL -o /docker-entrypoint-initdb.d/db-init.sql https://www.qn-cdn.getrebuild.com/pub/deploy/db-init_4.0.sql &&
exec docker-entrypoint.sh mysqld
"
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
@ -29,7 +24,7 @@ services:
- >
mkdir -p /app/rebuild/.rebuild &&
wget -O /app/rebuild/.rebuild/.rebuild https://www.qn-cdn.getrebuild.com/pub/deploy/.rebuild &&
java -Duser.timezone=Asia/Shanghai -DDataDirectory=/app/rebuild/.rebuild -jar /app/rebuild/rebuild-boot.jar
java -Dinitialize=yes -Duser.timezone=Asia/Shanghai -DDataDirectory=/app/rebuild/.rebuild -jar /app/rebuild/rebuild-boot.jar
ports:
- "18080:18080"
volumes:

16
.deploy/rebuild.conf Normal file
View file

@ -0,0 +1,16 @@
# ConfigurationItem
MobileUrl=
RbStoreUrl=
SecurityEnhanced=
TrustedAllUrl=
LibreofficeBin=
OnlyofficeServer=
OnlyofficeJwt=
MysqldumpBin=
UnsafeImgAccess=
# CommandArgs
_SmsDistributor=
_EmailDistributor=
_UseFrontJSAnywhere=
_TriggerMaxDepth=
_TriggerLessLog=

View file

@ -10,7 +10,7 @@
</parent>
<groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId>
<version>4.0.0</version>
<version>4.0.2</version>
<name>rebuild</name>
<description>Building your business-systems freely!</description>
<url>https://getrebuild.com/</url>

View file

@ -37,6 +37,7 @@ import com.rebuild.core.support.License;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.setup.DatabaseFixer;
import com.rebuild.core.support.setup.DockerInstaller;
import com.rebuild.core.support.setup.Installer;
import com.rebuild.core.support.setup.UpgradeDatabase;
import com.rebuild.utils.JSONable;
@ -74,11 +75,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/**
* Rebuild Version
*/
public static final String VER = "4.0.0";
public static final String VER = "4.0.2";
/**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/
public static final int BUILD = 4000006;
public static final int BUILD = 4000208;
static {
// Driver for DB
@ -133,17 +134,25 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
final Timer timer = new Timer("Boot-Timer");
try {
// v4.0.2 for Docker
DockerInstaller di = new DockerInstaller();
final boolean isNeedInitialize = di.isNeedInitialize();
if (isNeedInitialize) {
log.info("Initializing REBUILD for Docker container ...");
di.install();
}
if (Installer.isInstalled()) {
started = init();
if (started) {
final long time2 = System.currentTimeMillis() - time;
final long timeCost = System.currentTimeMillis() - time;
timer.schedule(new TimerTask() {
@Override
public void run() {
String localUrl = BootApplication.getLocalUrl(null);
String banner = RebuildBanner.formatSimple(
"REBUILD (" + VER + ") started successfully in " + time2 + " ms.",
"REBUILD (" + VER + ") started successfully in " + timeCost + " ms.",
" License : " + License.queryAuthority().values(),
"Access URLs : ",
" Local : " + localUrl,
@ -162,6 +171,8 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
}
}
}, 999);
if (isNeedInitialize) di.installAfter();
}
} else {
@ -215,7 +226,7 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
// 版本升级会清除缓存
int lastBuild = ObjectUtils.toInt(RebuildConfiguration.get(ConfigurationItem.AppBuild, true), 0);
// MINOR
if (lastBuild / 100000 != BUILD / 100000) {
if (lastBuild > 0 && lastBuild / 100000 != BUILD / 100000) {
log.warn("Clean up the cache when upgrading : {} from {}", BUILD, lastBuild);
Installer.clearAllCache();
RebuildConfiguration.set(ConfigurationItem.AppBuild, BUILD);

View file

@ -15,6 +15,7 @@ import com.rebuild.core.configuration.ConfigManager;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
@ -47,7 +48,7 @@ public class ClassificationManager implements ConfigManager {
}
/**
* 获取全名称包括父级 . 分割
* 获取全名称包括父级
*
* @param itemId
* @return
@ -61,39 +62,20 @@ public class ClassificationManager implements ConfigManager {
* @param itemId
* @return
*/
public String getColor(ID itemId) {
Item item = getItem(itemId);
return item == null ? null : item.Color;
}
/**
* @param itemId
* @return
*/
public String getCode(ID itemId) {
Item item = getItem(itemId);
return item == null ? null : item.Code;
}
/**
* @param itemId
* @return
*/
private Item getItem(ID itemId) {
final String ckey = "ClassificationITEM38-" + itemId;
public Item getItem(ID itemId) {
final String ckey = "ClassificationITEM40-" + itemId;
Item ditem = (Item) Application.getCommonsCache().getx(ckey);
if (ditem != null) {
return DELETED_ITEM.equals(ditem.Name) ? null : ditem;
}
Object[] o = Application.createQueryNoFilter(
"select name,fullName,code,color from ClassificationData where itemId = ?")
"select name,fullName,code,color,isHide from ClassificationData where itemId = ?")
.setParameter(1, itemId)
.unique();
if (o != null) ditem = new Item((String) o[0], (String) o[1], (String) o[2], (String) o[3]);
if (o != null) ditem = new Item((String) o[0], (String) o[1], (String) o[2], (String) o[3], ObjectUtils.toBool(o[4]));
// 可能已删除
if (ditem == null) ditem = new Item(DELETED_ITEM, null, null, null);
if (ditem == null) ditem = new Item(DELETED_ITEM, null, null, null, true);
Application.getCommonsCache().putx(ckey, ditem);
return DELETED_ITEM.equals(ditem.Name) ? null : ditem;
@ -188,18 +170,21 @@ public class ClassificationManager implements ConfigManager {
}
// Bean
static class Item implements Serializable {
@Getter
public static class Item implements Serializable {
private static final long serialVersionUID = -1903227875771376652L;
Item(String name, String fullName, String code, String color) {
Item(String name, String fullName, String code, String color, boolean isHide) {
this.Name = name;
this.FullName = fullName;
this.Code = code;
this.Color = color;
this.Hide = isHide;
}
final String Name;
final String FullName;
final String Code;
final String Color;
final boolean Hide;
}
}

View file

@ -30,8 +30,8 @@ public class EasyClassification extends EasyReference {
JSONObject map = (JSONObject) super.wrapValue(value);
if (map != null) {
map.remove("entity");
String color = ClassificationManager.instance.getColor((ID) value);
if (color != null) map.put("color", color);
ClassificationManager.Item item = ClassificationManager.instance.getItem((ID) value);
if (item != null && item.getColor() != null) map.put("color", item.getColor());
}
return map;
}

View file

@ -22,11 +22,6 @@ import java.util.Objects;
*/
public class Axis {
/**
* -- GETTER --
*
* @return
*/
@Getter
private Field field;
private FormatSort sort;

View file

@ -9,6 +9,7 @@ package com.rebuild.core.service.general;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.general.ClassificationManager;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.privileges.UserFilters;
@ -90,9 +91,12 @@ public class RecentlyUsedHelper {
// 是否符合条件
if (checkFilter != null) {
if (!QueryHelper.isMatchFilter(raw, checkFilter)) {
continue;
}
if (!QueryHelper.isMatchFilter(raw, checkFilter)) continue;
}
// fix:4.0.2
if (entityCode == EntityHelper.ClassificationData) {
ClassificationManager.Item item = ClassificationManager.instance.getItem(raw);
if (item == null || item.isHide()) continue;
}
try {

View file

@ -163,13 +163,13 @@ public enum ConfigurationItem {
|| SN.name().equals(name);
}
private Object defaultVal;
private Object defaultValue;
ConfigurationItem() {
}
ConfigurationItem(Object defaultVal) {
this.defaultVal = defaultVal;
this.defaultValue = defaultVal;
}
/**
@ -178,9 +178,9 @@ public enum ConfigurationItem {
* @return
*/
public Object getDefaultValue() {
if (defaultVal != null && defaultVal instanceof ConfigurationItem) {
return ((ConfigurationItem) defaultVal).getDefaultValue();
if (defaultValue != null && defaultValue instanceof ConfigurationItem) {
return ((ConfigurationItem) defaultValue).getDefaultValue();
}
return defaultVal;
return defaultValue;
}
}

View file

@ -252,10 +252,11 @@ public class DataListWrapper {
value = colorValue;
} else if (easyField.getDisplayType() == DisplayType.CLASSIFICATION) {
String color = ClassificationManager.instance.getColor((ID) originValue);
if (StringUtils.isNotBlank(color)) {
ClassificationManager.Item item = ClassificationManager.instance.getItem((ID) originValue);
if (item != null && StringUtils.isNotBlank(item.getColor())) {
value = JSONUtils.toJSONObject(
new String[]{ "text", "color" }, new Object[]{ value, color });
new String[]{ "text", "color" },
new Object[]{ value, item.getColor() });
}
}
}

View file

@ -0,0 +1,72 @@
/*!
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.core.support.setup;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application;
import com.rebuild.utils.JSONUtils;
import com.rebuild.utils.OshiUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
/**
* Docker 安装
*
* @author devezhao
* @since 2025/4/10
*/
@Slf4j
public class DockerInstaller extends Installer {
public DockerInstaller() {
super(buildInstallProps());
}
// FAKE
static JSONObject buildInstallProps() {
JSONObject installProps = new JSONObject();
JSONObject databaseProps = JSONUtils.toJSONObject(
new String[]{"dbName", "dbHost", "dbPort", "dbUser", "dbPassword"},
new String[]{"rebuild40", "mysql", "3306", "root", "rebuildP4wd"});
if (Application.devMode()) {
databaseProps.put("dbHost", "localhost");
databaseProps.put("dbUser", "rebuild");
databaseProps.put("dbPassword", "rebuild");
}
installProps.put("databaseProps", databaseProps);
return installProps;
}
@Override
public void install() throws Exception {
this.installDatabase();
}
/**
*/
public void installAfter() {
try {
this.installClassificationAsync();
} catch (Exception ex) {
log.error("Error installing classification data", ex);
}
}
/**
* @return
*/
public boolean isNeedInitialize() {
if (!OshiUtils.isDockerEnv()) return false;
if (!BooleanUtils.toBoolean(System.getProperty("initialize"))) return false;
if (Installer.isInstalled()) {
return !isRbDatabase();
}
return false;
}
}

View file

@ -0,0 +1,147 @@
/*!
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.utils;
import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.EncryptUtils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.jwt.JWT;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.api.user.AuthTokenManager;
import com.rebuild.core.RebuildException;
import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.integration.QiniuCloud;
import com.rebuild.web.admin.ConfigurationController;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import static com.rebuild.core.support.ConfigurationItem.OnlyofficeJwt;
import static com.rebuild.core.support.ConfigurationItem.OnlyofficeServer;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* @author devezhao
* @since 2025/2/26
*/
public class OnlyOfficeUtils {
/**
* OnlyOffice PDF
*
* @param path
* @return
* @throws PdfConverterException
*/
public static Path convertPdf(Path path) throws IOException {
final String ooServer = getOoServer();
final String ooJwt = RebuildConfiguration.get(OnlyofficeJwt);
String filename = path.getFileName().toString();
String filenameWithoutExt = filename.substring(0, filename.lastIndexOf("."));
JSONObject document = new JSONObject(true);
document.put("async", false);
document.put("key", "key-" + EncryptUtils.toMD5Hex(filename));
document.put("fileType", FileUtil.getSuffix(filename));
document.put("outputType", "pdf");
document.put("title", filenameWithoutExt);
String fileUrl = String.format("/filex/download/%s?_csrfToken=%s&temp=yes",
filename, AuthTokenManager.generateCsrfToken(90));
fileUrl = RebuildConfiguration.getHomeUrl(fileUrl);
document.put("url", fileUrl);
// Token
String tokenIfNeed = StringUtils.isBlank(ooJwt) ? null : JWT.create()
.addPayloads(document)
.setKey(ooJwt.getBytes(UTF_8))
.sign();
Map<String, String> reqHeaders = new HashMap<>();
reqHeaders.put("Content-Type", "application/json");
if (tokenIfNeed != null) {
reqHeaders.put("Authorization", "Bearer " + tokenIfNeed);
}
String res = OkHttpUtils.post(ooServer + "/converter", document, reqHeaders);
JSONObject resJson = JSON.parseObject(res);
String resFileUrl = resJson == null ? null : resJson.getString("fileUrl");
if (resFileUrl != null) {
File dest = new File(
RebuildConfiguration.getFileOfTemp(null), filenameWithoutExt + ".pdf");
OkHttpUtils.readBinary(resFileUrl, dest, null);
if (dest.exists()) return dest.toPath();
}
throw new RebuildException("Convert PDF fails (oo-ds) : " + resJson);
}
/**
* @param filepath
* @return
*/
public static Object[] buildPreviewParams(String filepath) {
final String ooJwt = RebuildConfiguration.get(OnlyofficeJwt);
final String filepathDecode = CodecUtils.urlDecode(filepath);
String[] fs = filepathDecode.split("/");
String filename = fs[fs.length - 1].split("\\?")[0];
JSONObject document = new JSONObject(true);
document.put("fileType", FileUtil.getSuffix(filename));
document.put("key", "key-" + EncryptUtils.toMD5Hex(filename));
document.put("title", QiniuCloud.parseFileName(filename));
// 外部地址
if (CommonsUtils.isExternalUrl(filepath)) {
document.put("url", filepath);
} else {
String fileUrl = String.format("/filex/download/%s?_csrfToken=%s",
filepath,
AuthTokenManager.generateCsrfToken(90));
fileUrl = RebuildConfiguration.getHomeUrl(fileUrl);
document.put("url", fileUrl);
}
// Token
String tokenIfNeed = StringUtils.isBlank(ooJwt) ? null : JWT.create()
.setPayload("document", document)
.setKey(ooJwt.getBytes(UTF_8))
.sign();
return new Object[]{document, tokenIfNeed};
}
/**
* @return
*/
public static String getOoServer() {
String ooServer = RebuildConfiguration.get(OnlyofficeServer);
Assert.notNull(ooServer, "[OnlyofficeServer] is not set");
if (ooServer.endsWith("/")) ooServer = ooServer.substring(0, ooServer.length() - 1);
return ooServer;
}
/**
* @return
*/
public static boolean isUseOoPreview() {
if (RebuildConfiguration.get(OnlyofficeServer) == null) return false;
return StringUtils.contains(
RebuildConfiguration.get(ConfigurationItem.PortalOfficePreviewUrl),
ConfigurationController.OO_PREVIEW_URL);
}
}

View file

@ -7,11 +7,6 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.utils;
import cn.devezhao.commons.CalendarUtils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.jwt.JWT;
import com.alibaba.fastjson.JSON;
import com.rebuild.api.user.AuthTokenManager;
import com.rebuild.core.Application;
import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.RebuildConfiguration;
@ -26,12 +21,8 @@ import org.jsoup.nodes.Element;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static com.rebuild.core.support.ConfigurationItem.OnlyofficeJwt;
import static com.rebuild.core.support.ConfigurationItem.OnlyofficeServer;
/**
@ -53,12 +44,12 @@ public class PdfConverter {
* @throws PdfConverterException
*/
public static Path convert(Path path, String type) throws PdfConverterException {
// v4.0
if (TYPE_PDF.equalsIgnoreCase(type) && RebuildConfiguration.get(OnlyofficeServer) != null) {
return ooConvertPdf(path);
}
try {
// v4.0
if (TYPE_PDF.equalsIgnoreCase(type) && RebuildConfiguration.get(OnlyofficeServer) != null) {
return OnlyOfficeUtils.convertPdf(path);
}
return convert(path, type, true);
} catch (IOException e) {
throw new PdfConverterException(e);
@ -165,47 +156,5 @@ public class PdfConverter {
FileUtils.writeStringToFile(sourceHtml, template.html(), "UTF-8");
}
/**
* OnlyOffice PDF
*
* @param path
* @return
* @throws PdfConverterException
*/
public static Path ooConvertPdf(Path path) throws PdfConverterException {
final String ooServer = RebuildConfiguration.get(OnlyofficeServer);
final String ooJwt = RebuildConfiguration.get(OnlyofficeJwt);
String filename = path.getFileName().toString();
Map<String, Object> document = new HashMap<>();
document.put("key", "key-" + filename.hashCode());
document.put("filetype", FileUtil.getSuffix(filename));
document.put("outputtype", "pdf");
document.put("async", "false");
document.put("title", filename);
String fileUrl = String.format("/filex/download/%s?_csrfToken=%s&temp=yes",
filename, AuthTokenManager.generateCsrfToken(90));
fileUrl = RebuildConfiguration.getHomeUrl(fileUrl);
document.put("url", fileUrl);
// Token
String token = JWT.create()
.setPayload("document", document)
.setExpiresAt(CalendarUtils.add(15, Calendar.MINUTE))
.setKey(ooJwt.getBytes())
.sign();
Map<String, String> httpHeaders = new HashMap<>();
httpHeaders.put("Content-Type", "application/json");
httpHeaders.put("Authorization", "Bearer " + token);
String res;
try {
res = OkHttpUtils.post(ooServer + "/converter", JSON.toJSON(document), httpHeaders);
} catch (IOException e) {
throw new PdfConverterException(e);
}
throw new UnsupportedOperationException("TODO:" + res);
}
}

View file

@ -276,7 +276,8 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
|| requestUri.startsWith("/rbmob/env")
|| requestUri.startsWith("/h5app-download")
|| requestUri.startsWith("/apiman/")
|| requestUri.startsWith("/commons/frontjs/use-frontjs");
|| requestUri.startsWith("/commons/frontjs/use-frontjs")
|| requestUri.startsWith("/commons/file-preview");
}
private boolean isHtmlRequest(String requestUri, HttpServletRequest request) {

View file

@ -71,6 +71,8 @@ public class ConfigurationController extends BaseController {
public static final String ETAG_DIMGLOGOTIME = "dimgLogoTime";
public static final String ETAG_DIMGBGIMGTIME = "dimgBgimgTime";
public static final String OO_PREVIEW_URL = "/commons/file-preview?src=";
@GetMapping("systems")
public ModelAndView pageSystems() {
ModelAndView mv = createModelAndView("/admin/system-cfg");
@ -101,8 +103,10 @@ public class ConfigurationController extends BaseController {
}
String dPortalOfficePreviewUrl = defaultIfBlank(data, ConfigurationItem.PortalOfficePreviewUrl);
if (StringUtils.isNotBlank(dPortalOfficePreviewUrl) && !RegexUtils.isUrl(dPortalOfficePreviewUrl)) {
return RespBody.errorl("无效文档预览服务地址");
if (StringUtils.isNotBlank(dPortalOfficePreviewUrl)) {
boolean valid = RegexUtils.isUrl(dPortalOfficePreviewUrl)
|| dPortalOfficePreviewUrl.contains(OO_PREVIEW_URL);
if (!valid) return RespBody.errorl("无效文档预览服务地址");
}
// 验证数字参数

View file

@ -7,15 +7,11 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.commons;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.CodecUtils;
import cn.hutool.core.io.FileUtil;
import cn.hutool.jwt.JWT;
import com.alibaba.fastjson.JSON;
import com.rebuild.api.user.AuthTokenManager;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.integration.QiniuCloud;
import com.rebuild.utils.CommonsUtils;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.JSONUtils;
import com.rebuild.utils.OnlyOfficeUtils;
import com.rebuild.web.BaseController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
@ -23,11 +19,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import static com.rebuild.core.support.ConfigurationItem.OnlyofficeJwt;
import static com.rebuild.core.support.ConfigurationItem.OnlyofficeServer;
/**
@ -41,42 +33,24 @@ import static com.rebuild.core.support.ConfigurationItem.OnlyofficeServer;
@Controller
public class FilePreviewer extends BaseController {
@GetMapping("/filex/preview/**")
@GetMapping("/commons/file-preview")
public ModelAndView ooPreview(HttpServletRequest request) {
final String ooServer = RebuildConfiguration.get(OnlyofficeServer);
final String ooJwt = RebuildConfiguration.get(OnlyofficeJwt);
final String filepathRaw = request.getRequestURI().split("/filex/preview/")[1];
final String filepath = CodecUtils.urlDecode(filepathRaw);
String[] fs = filepath.split("/");
String filename = fs[fs.length - 1];
Map<String, Object> document = new HashMap<>();
document.put("fileType", FileUtil.getSuffix(filename));
document.put("key", "key-" + filename.hashCode());
document.put("title", QiniuCloud.parseFileName(filename));
// 外部地址
if (CommonsUtils.isExternalUrl(filepath)) {
document.put("url", filepath);
} else {
String fileUrl = String.format("/filex/download/%s?_csrfToken=%s",
filepathRaw,
AuthTokenManager.generateCsrfToken(90));
fileUrl = RebuildConfiguration.getHomeUrl(fileUrl);
document.put("url", fileUrl);
}
// Token
String token = JWT.create()
.setPayload("document", document)
.setExpiresAt(CalendarUtils.add(15, Calendar.MINUTE))
.setKey(ooJwt.getBytes())
.sign();
String src = getParameterNotNull(request, "src");
Object[] ps = OnlyOfficeUtils.buildPreviewParams(src);
ModelAndView mv = createModelAndView("/common/oo-preview");
mv.getModel().put(OnlyofficeServer.name(), ooServer);
mv.getModel().put("_DocumentConfig", JSON.toJSON(document));
mv.getModel().put("_Token", token);
mv.getModel().put(OnlyofficeServer.name(), OnlyOfficeUtils.getOoServer());
mv.getModel().put("_DocumentConfig", ps[0]);
mv.getModel().put("_Token", ps[1]);
String[] user = new String[]{"REBUILD", "REBUILD"};
ID userid = AppUtils.getRequestUser(request);
if (userid != null) {
user = new String[]{userid.toString(), UserHelper.getName(userid)};
}
mv.getModel().put("_User",
JSONUtils.toJSONObject(new String[]{"id", "name"}, user));
return mv;
}
}

View file

@ -87,10 +87,10 @@ public class RecentlyUsedSearchController extends BaseController {
label = FieldValueHelper.NO_LABEL_PREFIX + id.toLiteral().toUpperCase();
}
String code = isClazz ? ClassificationManager.instance.getCode(id) : null;
ClassificationManager.Item item = isClazz ? ClassificationManager.instance.getItem(id) : null;
data.add(JSONUtils.toJSONObject(
new String[] { "id", "text", "code" },
new Object[] { id, label, code }));
new Object[] { id, label, item == null ? null : item.getCode() }));
}
if (useGroupName != null) {

View file

@ -38,6 +38,7 @@ import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.utils.JSONUtils;
import com.rebuild.utils.OnlyOfficeUtils;
import com.rebuild.utils.PdfConverter;
import com.rebuild.utils.RbAssert;
import com.rebuild.web.BaseController;
@ -175,7 +176,10 @@ public class ReportsController extends BaseController {
} else {
// 直接预览
boolean forcePreview = isHtml || isPdf || getBoolParameter(request, "preview");
boolean forcePreview = isHtml || getBoolParameter(request, "preview");
if (!forcePreview) {
if (isPdf && !OnlyOfficeUtils.isUseOoPreview()) forcePreview = true;
}
FileDownloader.downloadTempFile(response, output, fileName, forcePreview);
}
return null;

View file

@ -1,11 +1,11 @@
<th:block th:if="${commercial > 1}">
<script th:src="@{/assets/js/general/rb-easyaction.js}" type="text/babel"></script>
<script th:src="@{/assets/js/general/rb-sop.js}" type="text/babel"></script>
</th:block>
<script th:src="@{/assets/js/general/rb-forms.js}" type="text/babel"></script>
<script th:src="@{/assets/js/general/rb-forms.append.js}" type="text/babel"></script>
<script th:src="@{/assets/js/general/rb-forms.protable.js}" type="text/babel"></script>
<script th:src="@{/assets/js/general/rb-datalist.common.js}" type="text/babel"></script>
<th:block if="${commercial > 1}">
<script th:src="@{/assets/js/general/rb-easyaction.js}" type="text/babel"></script>
<script th:src="@{/assets/js/general/rb-sop.js}" type="text/babel"></script>
</th:block>
<script th:src="@{/assets/js/general/rb-approval.js}" type="text/babel"></script>
<script th:src="@{/assets/js/general/rb-advfilter.js}" type="text/babel"></script>
<script th:src="@{/assets/js/general/rb-assignshare.js}" type="text/babel"></script>

View file

@ -347,8 +347,8 @@ td.task-user > .avatar img {
.ps-toolbar .J_view > .btn > .icon,
.ps-toolbar .J_sorts > .btn > .icon {
font-size: 16px;
top: 10px;
font-size: 17px;
top: 11.5px;
left: 0;
position: absolute;
}

View file

@ -177,7 +177,8 @@ $(document).ready(() => {
setTimeout(() => {
RbGritter.create(
<RF>
<p>{$L('REBUILD 已成功更新至 %s 版本', rb.ver)}</p>
{$L('REBUILD 已成功更新至 %s 版本', rb.ver)}
<br />
<a href={`https://getrebuild.com/docs/dev/changelog?ver=${rb.ver}`} target="_blank" className="text-white link">
{$L('查看详情')}
</a>

View file

@ -131,7 +131,7 @@ class FeedsList extends React.Component {
<a>{item.createdBy[1]}</a>
<p className="text-muted fs-12 m-0">
<DateShow date={item.createdOn} />
<DateShow date={item.createdOn} showOrigin />
{item.createdOn !== item.modifiedOn && (
<span className="text-warning ml-1" title={`${$L('修改于')} ${item.modifiedOn}`}>
({$L('已修改')})

View file

@ -198,8 +198,11 @@ class RbPreview extends React.Component {
if (this._isDoc(fileName)) {
const isPdfType = fileName.toLowerCase().endsWith('.pdf')
const setPreviewUrl = function (url, fullUrl) {
let previewUrl = (rb._officePreviewUrl || 'https://view.officeapps.live.com/op/embed.aspx?src=') + $encode(url)
if (isPdfType) {
let previewUrl = rb._officePreviewUrl || 'https://view.officeapps.live.com/op/embed.aspx?src='
if (previewUrl.includes('/commons/file-preview?src=')) previewUrl = rb.baseUrl + previewUrl
previewUrl += $encode(url)
if (isPdfType && !previewUrl.includes('/commons/file-preview?src=')) {
if ($.browser.mobile) {
previewUrl = `${rb.baseUrl}/assets/lib/pdfjs/web/viewer.html?src=${$encode(url)}`
} else {

View file

@ -2119,7 +2119,10 @@ class RbFormReference extends RbFormElement {
// 新建记录时触发回填
const props = this.props
if (this._isNew && props.value && props.value.id) {
setTimeout(() => this.triggerAutoFillin(props.value.id), 200)
// fix: 4.0.2 #IC0GPI 复制时无需回填
if (props._disableAutoFillin !== true) {
setTimeout(() => this.triggerAutoFillin(props.value.id), 200)
}
}
}

View file

@ -283,7 +283,7 @@ class ProTable extends React.Component {
this._addLine(model, index)
}
_addLine(model, index) {
_addLine(model, index, _disableAutoFillin) {
// 明细未配置或出错
if (!model) {
if (this.state.hasError) RbHighbar.create(this.state.hasError)
@ -296,7 +296,7 @@ class ProTable extends React.Component {
const FORM = (
<InlineForm entity={entityName} id={model.id} rawModel={model} $$$parent={this} $$$main={this.props.$$$main} key={lineKey} ref={ref} _componentDidUpdate={() => this._componentDidUpdate()}>
{model.elements.map((item) => {
return detectElement({ ...item, colspan: 4 })
return detectElement({ ...item, colspan: 4, _disableAutoFillin: _disableAutoFillin === true })
})}
</InlineForm>
)
@ -320,7 +320,7 @@ class ProTable extends React.Component {
// force New
delete data.metadata.id
this._formdataRebuild(data, (res) => this._addLine(res.data, index))
this._formdataRebuild(data, (res) => this._addLine(res.data, index, true))
}
removeLine(lineKey) {

View file

@ -470,7 +470,6 @@ class SelectReport extends React.Component {
<div>
<ul className="list-unstyled">
{(this.state.reports || []).map((item) => {
rb._officePreviewUrl = 111
const reportUrl = `${rb.baseUrl}/app/${this.props.entity}/report/export?report=${item.id}&record=${this.props.id}`
const showPdf = (item.outputType || '').includes('pdf')
const showHtml = item.outputType !== 'html5' && (item.outputType || '').includes('html')

View file

@ -747,7 +747,7 @@ const RbViewPage = {
$('.J_sharingList').parent().remove()
}
} else if (k === 'createdOn' || k === 'modifiedOn') {
renderRbcomp(<DateShow date={v} />, $el[0])
renderRbcomp(<DateShow date={v} showOrigin />, $el)
} else {
$(`<span>${v}</span>`).appendTo($el.empty())
}
@ -933,6 +933,7 @@ const RbViewPage = {
// 记录转换
initTransform(config) {
config.forEach((item) => {
if ($isSysMask(item.transName)) return // v4.0.2
const $item = $(`<a class="dropdown-item"><i class="icon zmdi zmdi-${item.icon}"></i>${item.transName || item.entityLabel}</a>`)
$item.on('click', () => renderRbcomp(<DlgTransform {...item} sourceRecord={this.__id} />))
$('.J_transform .dropdown-divider').before($item)

View file

@ -824,8 +824,20 @@ const UserShow = function (props) {
}
// ~~ 日期显示
const DateShow = function ({ date, title }) {
return date ? <span title={title || date}>{$fromNow(date)}</span> : null
class DateShow extends React.Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
const date = this.props.date
return date ? (
<span title={this.props.title || date} onClick={() => this.setState({ _showOrigin: !this.state._showOrigin })}>
{this.props.showOrigin && this.state._showOrigin ? date : $fromNow(date)}
</span>
) : null
}
}
// ~~ 记录选择器

View file

@ -1203,7 +1203,7 @@ var $isFullUrl = function (url) {
// Mask prefix `SYS `
var $isSysMask = function (label) {
return label && (label.startsWith('SYS ') || label.contains('.SYS ')) && location.href.indexOf('/admin/') === -1
return label && (label.startsWith('SYS ') || label.contains('.SYS ') || label.contains('#SYS')) && location.href.indexOf('/admin/') === -1
}
// 颜色

View file

@ -95,7 +95,7 @@
function setZoom(z) {
zoom += z
if (zoom < 20) zoom = 20
if (zoom > 500) zoom = 500
if (zoom > 1000) zoom = 1000
$('.zoom > span').text(zoom + '%')
$node.css('transform', `scale(${zoom / 100})`)

View file

@ -17,14 +17,18 @@
<div id="viewer" style="width: 100%; height: 100%"></div>
<script th:src="${OnlyofficeServer + '/web-apps/apps/api/documents/api.js'}"></script>
<script>
// https://api.onlyoffice.com/docs/docs-api/usage-api/config/
new DocsAPI.DocEditor('viewer', {
editorConfig: {
mode: 'view',
lang: 'zh',
user: { id: '001-0000000000000000', name: 'REBUILD' },
toolbar: false,
menu: false,
user: [(${_User})],
},
token: '[[${_Token}]]',
document: [(${_DocumentConfig})],
token: '[[${_Token ?:""}]]',
type: 'embedded',
})
</script>
</body>

View file

@ -87,7 +87,7 @@
</div>
</div>
</div>
<div id="plan-boxes" data-fullcontent="115"></div>
<div id="plan-boxes" data-fullcontent="116"></div>
</div>
</div>
</div>