Merge pull request #362 from getrebuild/wxwork-dingtalk

Wxwork dingtalk
This commit is contained in:
devezhao 2021-07-21 23:13:30 +08:00 committed by GitHub
commit 7778575250
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 705 additions and 194 deletions

Binary file not shown.

2
@rbv

@ -1 +1 @@
Subproject commit 276e88e1327caf8f6dcc9b08c16ba7b67010e396
Subproject commit 1536f8a9f00c61b7868bb16b898b980a61218c5f

41
pom.xml
View file

@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<version>2.5.2</version>
<relativePath/>
</parent>
<groupId>com.rebuild</groupId>
@ -42,7 +42,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.11.0</version>
<version>1.12.0</version>
<executions>
<execution>
<id>install node and npm</id>
@ -223,10 +223,11 @@
<optional>true</optional>
</dependency>
-->
<!-- Use SPEC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.3</version>
<version>5.3.9</version>
</dependency>
<dependency>
@ -282,17 +283,17 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.2</version>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
<version>2.10.9.2</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
@ -302,7 +303,7 @@
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.4.0</version>
<version>7.8.0</version>
</dependency>
<dependency>
<groupId>com.github.whvcse</groupId>
@ -317,12 +318,12 @@
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
<version>1.14.1</version>
</dependency>
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.7.8</version>
<version>portable-1.8.2</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
@ -342,7 +343,7 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.8</version>
<version>2.2.10</version>
<exclusions>
<exclusion>
<artifactId>ehcache</artifactId>
@ -388,17 +389,17 @@
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.2.3</version>
<version>5.2.6</version>
</dependency>
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.13</version>
<version>0.4.14</version>
</dependency>
<dependency>
<groupId>es.moki.ratelimitj</groupId>
<artifactId>ratelimitj-inmemory</artifactId>
<version>0.6.0</version>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
@ -408,7 +409,7 @@
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.0</version>
<version>3.16.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@ -418,7 +419,17 @@
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>5.7.0</version>
<version>5.8.0</version>
</dependency>
<!-- Local .deps -->
<dependency>
<groupId>local</groupId>
<artifactId>dingtalk-sdk</artifactId>
<version>20210719</version>
<scope>system</scope>
<systemPath>${basedir}/.deps/taobao-sdk-java-auto_1479188381469-20210716.jar</systemPath>
</dependency>
</dependencies>
</project>

View file

@ -40,7 +40,7 @@ public class AuthTokenManager {
public static String generateToken(ID user, int expires) {
String token = String.format("%s,%d,%s,v1",
user, System.currentTimeMillis(), CodecUtils.randomCode(10));
token = CodecUtils.base64UrlEncode(token);
token = CodecUtils.base64UrlEncode(token); // 64bit
Application.getCommonsCache().putx(TOKEN_PREFIX + token, user, expires);
return token;
}

View file

@ -58,7 +58,7 @@ public class LoginToken extends BaseApi {
*
* @param user
* @param password
* @return
* @return 返回 null 表示成功
*/
public static String checkUser(String user, String password) {
if (!Application.getUserStore().existsUser(user)) {

View file

@ -155,9 +155,11 @@ public class BootEnvironmentPostProcessor implements EnvironmentPostProcessor, I
*/
public static String getProperty(String name, String defaultValue) {
String value = null;
if (ConfigurationItem.DataDirectory.name().equalsIgnoreCase(name)) {
if (ConfigurationItem.DataDirectory.name().equalsIgnoreCase(name)
|| ConfigurationItem.MobileUrl.name().equalsIgnoreCase(name)) {
value = StringUtils.defaultIfBlank(
System.getProperty("DataDirectory"), System.getProperty(V2_PREFIX + "DataDirectory"));
System.getProperty(name), System.getProperty(V2_PREFIX + name));
} else if (ENV_HOLD != null) {
if (!(name.startsWith(V2_PREFIX) || name.contains("."))) {
name = V2_PREFIX + name;

View file

@ -20,6 +20,7 @@ import com.rebuild.core.privileges.UserService;
import com.rebuild.core.service.DataSpecificationException;
import com.rebuild.core.service.general.QuickCodeReindexTask;
import com.rebuild.core.support.i18n.Language;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
@ -29,6 +30,7 @@ import org.springframework.stereotype.Service;
* @author devezhao zhaofang123@gmail.com
* @since 2019/04/10
*/
@Slf4j
@Service
public class ClassificationService extends BaseConfigurationService implements AdminGuard {
@ -87,7 +89,7 @@ public class ClassificationService extends BaseConfigurationService implements A
} finally {
long cost = System.currentTimeMillis() - start;
if (cost > 2000 || Application.devMode()) {
LOG.info("Reindex FullName [ " + itemId + " ] in " + cost + " ms");
log.info("Reindex FullName [ {} ] in {} ms", itemId, cost);
}
}
});

View file

@ -177,6 +177,7 @@ public class EntityHelper {
public static final int RoleMember = 5;
public static final int Team = 6;
public static final int TeamMember = 7;
public static final int ExternalUser = 8;
// 配置

View file

@ -17,6 +17,7 @@ import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.privileges.AdminGuard;
import com.rebuild.core.service.BaseService;
import com.rebuild.core.service.ServiceSpec;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
@ -25,6 +26,7 @@ import org.springframework.stereotype.Service;
* @author zhaofang123@gmail.com
* @since 08/03/2018
*/
@Slf4j
@Service
public class MetaEntityService extends BaseService implements AdminGuard {
@ -76,7 +78,8 @@ public class MetaEntityService extends BaseService implements AdminGuard {
}
if (usedArray.length > 0) {
LOG.warn("deleted configuration of entity [ " + delEntity.getName() + " ] in [ " + conf + " ] : " + usedArray.length);
log.warn("Deleted configuration of entity [ {} ] in [ {} ] : {}",
delEntity.getName(), conf, usedArray.length);
}
}

View file

@ -19,6 +19,7 @@ import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.privileges.AdminGuard;
import com.rebuild.core.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
@ -27,6 +28,7 @@ import org.springframework.stereotype.Service;
* @author zhaofang123@gmail.com
* @since 08/03/2018
*/
@Slf4j
@Service
public class MetaFieldService extends BaseService implements AdminGuard {
@ -70,7 +72,8 @@ public class MetaFieldService extends BaseService implements AdminGuard {
}
if (usedArray.length > 0) {
LOG.warn("deleted configuration of field [ " + field.getOwnEntity().getName() + "." + field.getName() + " ] in [ " + who + " ] : " + usedArray.length);
log.warn("Deleted configuration of field [ {}.{} ] in [ {} ] : {}",
field.getOwnEntity().getName(), field.getName(), who, usedArray.length);
if ("PickList".equals(who)) {
PickListManager.instance.clean(field);

View file

@ -34,6 +34,7 @@ import com.rebuild.core.support.task.TaskExecutors;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.BlockList;
import com.rebuild.utils.CommonsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
@ -46,6 +47,7 @@ import java.util.Map;
* @author zhaofang123@gmail.com
* @since 07/25/2018
*/
@Slf4j
@Service
public class UserService extends BaseServiceImpl {
@ -155,7 +157,7 @@ public class UserService extends BaseServiceImpl {
try {
UserHelper.generateAvatar(record.getString("fullName"), true);
} catch (Exception ex) {
LOG.error(null, ex);
log.error(null, ex);
}
}
}
@ -381,7 +383,7 @@ public class UserService extends BaseServiceImpl {
newUserId);
content += String.format("[%s](%s)", Language.L("点击开始激活"), viewUrl);
Message message = MessageBuilder.createMessage(ADMIN_USER, content, newUserId);
Message message = MessageBuilder.createMessage(ADMIN_USER, content, Message.TYPE_DEFAULT, newUserId);
Application.getNotifications().send(message);
return newUserId;

View file

@ -11,8 +11,6 @@ import cn.devezhao.persist4j.PersistManagerFactory;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.Application;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 基础服务类
@ -22,8 +20,6 @@ import org.slf4j.LoggerFactory;
*/
public abstract class BaseService implements ServiceSpec {
protected final Logger LOG = LoggerFactory.getLogger(getClass());
private final PersistManagerFactory aPMFactory;
protected BaseService(PersistManagerFactory aPMFactory) {

View file

@ -30,11 +30,10 @@ public class MessageBuilder {
/**
* @param toUser
* @param message
* @param recordId
* @return
*/
public static Message createMessage(ID toUser, String message, ID recordId) {
return new Message(null, toUser, message, recordId, Message.TYPE_DEFAULT);
public static Message createMessage(ID toUser, String message) {
return new Message(null, toUser, message, null, Message.TYPE_DEFAULT);
}
/**
@ -50,22 +49,22 @@ public class MessageBuilder {
/**
* @param toUser
* @param message
* @param type
* @param recordId
* @param relatedRecord
* @return
*/
public static Message createMessage(ID toUser, String message, int type, ID recordId) {
return new Message(null, toUser, message, recordId, type);
public static Message createApproval(ID toUser, String message, ID relatedRecord) {
return new Message(null, toUser, message, relatedRecord, Message.TYPE_APPROVAL);
}
/**
* @param toUser
* @param message
* @param recordId
* @param type
* @param relatedRecord
* @return
*/
public static Message createApproval(ID toUser, String message, ID recordId) {
return new Message(null, toUser, message, recordId, Message.TYPE_APPROVAL);
public static Message createMessage(ID toUser, String message, int type, ID relatedRecord) {
return new Message(null, toUser, message, relatedRecord, type);
}
// --

View file

@ -0,0 +1,26 @@
/*
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.service.notification;
import cn.devezhao.persist4j.engine.ID;
/**
* 消息分发
*
* @author devezhao
* @since 2021/7/20
*/
public interface MessageDistributor {
/**
* @param message
* @param messageId
* @return
*/
boolean send(Message message, ID messageId);
}

View file

@ -8,12 +8,15 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.notification;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.commons.ThreadPool;
import cn.devezhao.persist4j.PersistManagerFactory;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.Application;
import com.rebuild.core.UserContextHolder;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
@ -22,6 +25,7 @@ import org.springframework.stereotype.Service;
* @author devezhao
* @since 10/17/2018
*/
@Slf4j
@Service
public class NotificationService extends BaseService {
@ -66,6 +70,8 @@ public class NotificationService extends BaseService {
}
}
// --
/**
* 发送消息
*
@ -82,10 +88,27 @@ public class NotificationService extends BaseService {
if (message.getRelatedRecord() != null) {
record.setID("relatedRecord", message.getRelatedRecord());
}
this.create(record);
record = this.create(record);
// 分发消息
final ID messageId = record.getPrimary();
ThreadPool.exec(() -> {
String[] mdNames = Application.getContext().getBeanNamesForType(MessageDistributor.class);
for (String md : mdNames) {
try {
boolean s = ((MessageDistributor) Application.getContext().getBean(md)).send(message, messageId);
log.info("Distribute message : {} >> {}", messageId, s);
} catch (Exception ex) {
log.error("Distribute message failed : {}", messageId, ex);
}
}
});
}
/**
* 获取未读消息数
*
* @param user
* @return
*/
@ -104,4 +127,15 @@ public class NotificationService extends BaseService {
Application.getCommonsCache().putx(ckey, count);
return count;
}
/**
* 设为已读
*
* @param messageId
*/
public void makeRead(ID messageId) {
Record record = EntityHelper.forUpdate(messageId, UserContextHolder.getUser());
record.setBoolean("unread", false);
this.update(record);
}
}

View file

@ -20,6 +20,7 @@ import com.rebuild.core.service.trigger.ActionContext;
import com.rebuild.core.service.trigger.ActionType;
import com.rebuild.core.service.trigger.TriggerAction;
import com.rebuild.core.support.general.ContentWithFieldVars;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.integration.SMSender;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
@ -85,7 +86,8 @@ public class SendNotification implements TriggerAction {
String message = content.getString("content");
message = ContentWithFieldVars.replaceWithRecord(message, context.getSourceRecord());
String emailSubject = StringUtils.defaultIfBlank(content.getString("title"), "你有一条新通知");
String emailSubject = content.getString("title");
if (StringUtils.isBlank(emailSubject)) emailSubject = Language.L("你有一条新通知");
for (ID user : toUsers) {
if (type == TYPE_MAIL) {
@ -101,7 +103,7 @@ public class SendNotification implements TriggerAction {
}
} else { // TYPE_NOTIFICATION
Message m = MessageBuilder.createMessage(user, message, context.getSourceRecord());
Message m = MessageBuilder.createMessage(user, message, Message.TYPE_DEFAULT, context.getSourceRecord());
Application.getNotifications().send(m);
}
}

View file

@ -17,10 +17,12 @@ import com.rebuild.core.support.i18n.LanguageBundle;
*/
public enum ConfigurationItem {
// 仅命令行指定
DataDirectory, // 数据目录
MobileUrl, // 移动端地址
// 系统指定
SN, DBVer, AppBuild,
// 数据目录命令行指定
DataDirectory,
// 缓存服务安装/配置文件指定
CacheHost, CachePort, CacheUser, CachePassword,
@ -89,6 +91,12 @@ public enum ConfigurationItem {
// 登录密码过期时间
PasswordExpiredDays(0),
// DingTalk
DingtalkAgentid, DingtalkAppkey, DingtalkAppsecret, DingtalkCorpid,
DingtalkPushAeskey, DingtalkPushToken,
// WxWork
WxworkCorpid, WxworkAgentid, WxworkSecret
;
private Object defaultVal;

View file

@ -36,7 +36,7 @@ public class DataMasking {
int starLen = textLen - len3 * 2;
return text.substring(0, len3)
+ StringUtils.repeat("*", Math.min(starLen, 20))
+ text.substring(len3 * 2);
+ text.substring(textLen - len3 * 2);
}
}

View file

@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.support;
import com.rebuild.core.BootEnvironmentPostProcessor;
import com.rebuild.core.RebuildException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
@ -145,17 +146,33 @@ public class RebuildConfiguration extends KVStorage {
*/
public static String getHomeUrl(String path) {
String homeUrl = get(ConfigurationItem.HomeURL);
if (!homeUrl.endsWith("/")) {
homeUrl += "/";
if (path != null) homeUrl = joinPath(homeUrl, path);
else if (!homeUrl.endsWith("/")) homeUrl += "/";
return homeUrl;
}
/**
* 获取绝对 URL H5
*
* @param path
* @return
* @see #getHomeUrl(String)
*/
public static String getMobileUrl(String path) {
String mobileUrl = BootEnvironmentPostProcessor.getProperty(ConfigurationItem.MobileUrl.name());
if (mobileUrl != null) {
return path == null ? mobileUrl : joinPath(mobileUrl, path);
}
if (path != null) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return homeUrl + path;
}
return homeUrl;
mobileUrl = "/h5app/";
if (path != null) mobileUrl = joinPath(mobileUrl, path);
return getHomeUrl(mobileUrl);
}
static String joinPath(String path1, String path2) {
if (path1.endsWith("/")) path1 = path1.substring(0, path1.length() - 1);
if (path2.startsWith("/")) path2 = path2.substring(1);
return path1 + "/" + path2;
}
/**
@ -203,6 +220,15 @@ public class RebuildConfiguration extends KVStorage {
return s == null ? (Integer) name.getDefaultValue() : NumberUtils.toInt(s);
}
/**
* @param name
* @return
*/
public static long getLong(ConfigurationItem name) {
String s = get(name);
return s == null ? (Long) name.getDefaultValue() : NumberUtils.toLong(s);
}
/**
* @param name
* @return

View file

@ -11,6 +11,8 @@ import cn.devezhao.commons.ThrowableUtils;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.commons.web.WebUtils;
import cn.devezhao.persist4j.engine.ID;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.rebuild.api.user.AuthTokenManager;
import com.rebuild.core.Application;
import com.rebuild.core.BootApplication;
@ -222,4 +224,15 @@ public class AppUtils {
String ua = request.getHeader("user-agent");
return ua != null && ua.contains("Trident/") && ua.contains("rv:11.");
}
/**
* 是否移动端
*
* @param request
* @return
*/
public static boolean isMobile(HttpServletRequest request) {
UserAgent ua = UserAgentUtil.parse(request.getHeader("user-agent"));
return ua != null && ua.isMobile();
}
}

View file

@ -7,13 +7,11 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.utils;
import com.alibaba.fastjson.JSON;
import com.rebuild.core.Application;
import com.rebuild.core.support.RebuildConfiguration;
import lombok.extern.slf4j.Slf4j;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils;
import org.springframework.http.HttpHeaders;
@ -95,35 +93,47 @@ public class HttpUtils {
* POST
*
* @param url
* @param formData
* @param reqData
* @return
* @throws IOException
*/
public static String post(String url, Map<String, Object> formData) throws IOException {
return post(url, formData, null);
public static String post(String url, Object reqData) throws IOException {
return post(url, reqData, null);
}
/**
* POST with Headers
*
* @param url
* @param formData
* @param reqData
* @param headers
* @return
* @throws IOException
*/
public static String post(String url, Map<String, Object> formData, Map<String, String> headers) throws IOException {
FormBody.Builder formBuilder = new FormBody.Builder();
if (formData != null && !formData.isEmpty()) {
for (Map.Entry<String, Object> e : formData.entrySet()) {
public static String post(String url, Object reqData, Map<String, String> headers) throws IOException {
RequestBody requestBody;
// JSON
if (reqData instanceof JSON) {
requestBody = RequestBody.create(((JSON) reqData).toJSONString(), MediaType.parse("application/json"));
}
// Map
else if (reqData instanceof Map) {
FormBody.Builder formBuilder = new FormBody.Builder();
for (Map.Entry<?, ?> e : ((Map<?, ?>) reqData).entrySet()) {
Object v = e.getValue();
formBuilder.add(e.getKey(), v == null ? StringUtils.EMPTY : v.toString());
formBuilder.add(e.getKey().toString(), v == null ? StringUtils.EMPTY : v.toString());
}
requestBody = formBuilder.build();
}
// Text
else {
requestBody = RequestBody.create(reqData.toString(), MediaType.parse("text/plain"));
}
Request.Builder builder = new Request.Builder().url(url);
Request request = useHeaders(builder, headers)
.post(formBuilder.build())
.post(requestBody)
.build();
long ms = System.currentTimeMillis();

View file

@ -25,6 +25,7 @@ import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.integration.QiniuCloud;
import com.rebuild.core.support.integration.SMSender;
import com.rebuild.utils.JSONUtils;
import com.rebuild.utils.RbAssert;
import com.rebuild.web.BaseController;
import com.rebuild.web.RebuildWebConfigurer;
import lombok.extern.slf4j.Slf4j;
@ -275,4 +276,69 @@ public class ConfigurationController extends BaseController {
}
}
}
// DingTalk
@GetMapping("integration/dingtalk")
public ModelAndView pageIntegrationDingtalk() {
RbAssert.isCommercial(
Language.L("免费版不支持钉钉集成 [(查看详情)](https://getrebuild.com/docs/rbv-features)"));
ModelAndView mv = createModelAndView("/admin/integration/dingtalk");
for (ConfigurationItem item : ConfigurationItem.values()) {
String name = item.name();
if (name.startsWith("Dingtalk")) {
String value = RebuildConfiguration.get(item);
if (value != null && item == ConfigurationItem.DingtalkAppsecret) {
value = DataMasking.masking(value);
}
mv.getModel().put(name, value);
}
}
String homeUrl = RebuildConfiguration.getHomeUrl("/user/dingtalk");
mv.getModel().put("_DingtalkHomeUrl", homeUrl);
return mv;
}
@PostMapping("integration/dingtalk")
public RespBody postIntegrationDingtalk(@RequestBody JSONObject data) {
setValues(data);
return RespBody.ok();
}
// WxWork
@GetMapping("integration/wxwork")
public ModelAndView pageIntegrationWxwork() {
RbAssert.isCommercial(
Language.L("免费版不支持企业微信集成 [(查看详情)](https://getrebuild.com/docs/rbv-features)"));
ModelAndView mv = createModelAndView("/admin/integration/wxwork");
for (ConfigurationItem item : ConfigurationItem.values()) {
String name = item.name();
if (name.startsWith("Wxwork")) {
String value = RebuildConfiguration.get(item);
if (value != null && item == ConfigurationItem.WxworkSecret) {
value = DataMasking.masking(value);
}
mv.getModel().put(name, value);
}
}
String homeUrl = RebuildConfiguration.getHomeUrl("/user/wxwork");
mv.getModel().put("_WxworkHomeUrl", homeUrl);
mv.getModel().put("_WxworkAuthCallUrl", homeUrl.split("//")[1].split("/")[0]);
return mv;
}
@PostMapping("integration/wxwork")
public RespBody postIntegrationWxwork(@RequestBody JSONObject data) {
setValues(data);
return RespBody.ok();
}
}

View file

@ -8,8 +8,12 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.commons;
import cn.devezhao.commons.web.ServletUtils;
import com.rebuild.core.support.License;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.utils.AppUtils;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.web.BaseController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@ -27,8 +31,12 @@ import java.io.IOException;
public class CommonPageView extends BaseController {
@GetMapping("/")
public void index(HttpServletResponse response) throws IOException {
response.sendRedirect("user/login");
public void index(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (AppUtils.isMobile(request) && License.isRbvAttached()) {
response.sendRedirect(RebuildConfiguration.getMobileUrl("/"));
} else {
response.sendRedirect("user/login");
}
}
@GetMapping("/*.txt")
@ -39,7 +47,7 @@ public class CommonPageView extends BaseController {
String content = CommonsUtils.getStringOfRes("web/" + url);
if (content == null) {
response.sendError(404);
response.sendError(HttpStatus.NOT_FOUND.value());
} else {
ServletUtils.setContentType(response, ServletUtils.CT_PLAIN);
ServletUtils.write(response, content);

View file

@ -9,6 +9,7 @@ package com.rebuild.web.commons;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.web.BaseController;
import com.rebuild.web.WebConstants;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@ -24,7 +25,9 @@ public class RbvMissingController extends BaseController {
@GetMapping({"/h5app/**"})
public ModelAndView pageH5app() {
return ErrorPageView.createErrorPage(
ModelAndView mv = ErrorPageView.createErrorPage(
Language.L("免费版不支持手机访问功能 [(查看详情)](https://getrebuild.com/docs/rbv-features)"));
mv.getModelMap().put(WebConstants.$BUNDLE, Language.getCurrentBundle());
return mv;
}
}

View file

@ -31,7 +31,8 @@ import java.text.MessageFormat;
@Controller
public class ListAndViewRedirection extends BaseController {
@GetMapping("/app/list-and-view")
// H5: "/app/redirect"
@GetMapping({ "/app/list-and-view", "/app/redirect" })
public void redirect(@IdParam ID anyId, HttpServletResponse response) throws IOException {
String url = null;

View file

@ -7,16 +7,15 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.notification;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.rebuild.api.RespBody;
import com.rebuild.core.Application;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.service.approval.ApprovalState;
import com.rebuild.core.service.notification.MessageBuilder;
import com.rebuild.core.support.i18n.I18nUtils;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BaseController;
import org.springframework.web.bind.annotation.GetMapping;
@ -25,6 +24,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
/**
@ -60,8 +60,9 @@ public class NotificationController extends BaseController {
if ("ALL".equalsIgnoreCase(ids)) {
Object[][] unreads = Application.createQueryNoFilter(
"select messageId from Notification where toUser = ?")
"select messageId from Notification where toUser = ? and unread = ?")
.setParameter(1, user)
.setParameter(2, true)
.array();
StringBuilder sb = new StringBuilder();
@ -71,13 +72,9 @@ public class NotificationController extends BaseController {
ids = sb.toString();
}
for (String id : ids.split(",")) {
if (!ID.isId(id)) continue;
Record record = EntityHelper.forUpdate(ID.valueOf(id), user);
record.setBoolean("unread", false);
Application.getNotifications().update(record);
}
Arrays.stream(ids.split(","))
.filter(ID::isId)
.forEach(id -> Application.getNotifications().makeRead(ID.valueOf(id)));
return RespBody.ok();
}
@ -141,19 +138,20 @@ public class NotificationController extends BaseController {
.setParameter(1, approvalStep)
.unique();
if (stepState == null) {
m[3] = new Object[]{0};
m[3] = new Object[] { 0 };
} else {
boolean canceled = (Boolean) stepState[0];
ApprovalState state = (ApprovalState) ApprovalState.valueOf((Integer) stepState[1]);
if (state == ApprovalState.DRAFT) {
m[3] = canceled ? new Object[]{2, "已处理"} : new Object[]{1, "待处理"};
boolean canceled = (Boolean) stepState[0];
m[3] = canceled
? new Object[] { 2, Language.L("已处理") }
: new Object[] { 1, Language.L("待处理") };
} else if (state == ApprovalState.APPROVED) {
m[3] = new Object[]{10, "已同意"};
m[3] = new Object[] { 10, Language.L("已同意") };
} else if (state == ApprovalState.REJECTED) {
m[3] = new Object[]{11, "已驳回"};
m[3] = new Object[] { 11, Language.L("已驳回") };
}
}
array[i] = m;
}

View file

@ -15,7 +15,10 @@ import com.rebuild.api.RespBody;
import com.rebuild.core.Application;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.privileges.UserService;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.core.service.DataSpecificationException;
import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.VerfiyCode;
import com.rebuild.core.support.i18n.I18nUtils;
import com.rebuild.core.support.i18n.Language;
@ -41,7 +44,25 @@ public class UserSettings extends EntityController {
@GetMapping("/user")
public ModelAndView pageUser(HttpServletRequest request) {
ModelAndView mv = createModelAndView("/settings/user-settings");
mv.getModelMap().put("user", Application.getUserStore().getUser(getRequestUser(request)));
User user = Application.getUserStore().getUser(getRequestUser(request));
mv.getModelMap().put("user", user);
if (RebuildConfiguration.get(ConfigurationItem.DingtalkCorpid) != null) {
Object[] dingtalkUser = Application.createQueryNoFilter(
"select appUser from ExternalUser where bindUser = ? and appType = 1")
.setParameter(1, user.getId())
.unique();
if (dingtalkUser != null) mv.getModelMap().put("dingtalkUser", dingtalkUser[0]);
}
if (RebuildConfiguration.get(ConfigurationItem.WxworkCorpid) != null) {
Object[] wxworkUser = Application.createQueryNoFilter(
"select appUser from ExternalUser where bindUser = ? and appType = 2")
.setParameter(1, user.getId())
.unique();
if (wxworkUser != null) mv.getModelMap().put("wxworkUser", wxworkUser[0]);
}
return mv;
}
@ -146,4 +167,19 @@ public class UserSettings extends EntityController {
}
return RespBody.ok();
}
@PostMapping("/cancel-external-user")
public RespBody cancelExternalUser(HttpServletRequest request) {
int appType = getIntParameter(request, "type", 0);
Object[] externalUser = Application.createQueryNoFilter(
"select userId from ExternalUser where bindUser = ? and appType = ?")
.setParameter(1, getRequestUser(request))
.setParameter(2, appType)
.unique();
if (externalUser != null) {
Application.getCommonsService().delete((ID) externalUser[0]);
}
return RespBody.ok();
}
}

View file

@ -62,17 +62,14 @@ import java.util.Map;
public class LoginController extends BaseController {
public static final String CK_AUTOLOGIN = "rb.alt";
private static final String SK_NEED_VCODE = "needLoginVCode";
public static final String SK_USER_THEME = "currentUseTheme";
private static final String DEFAULT_HOME = "../dashboard/home";
private static final String SK_NEED_VCODE = "needLoginVCode";
@GetMapping("login")
public ModelAndView checkLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
final String homeUrl = "../dashboard/home";
if (AppUtils.getRequestUser(request) != null) {
response.sendRedirect(DEFAULT_HOME);
response.sendRedirect(homeUrl);
return null;
}
@ -83,7 +80,7 @@ public class LoginController extends BaseController {
if (tokenUser != null) {
loginSuccessed(request, response, tokenUser, false);
String nexturl = StringUtils.defaultIfBlank(request.getParameter("nexturl"), DEFAULT_HOME);
String nexturl = getParameter(request, "nexturl", homeUrl);
response.sendRedirect(CodecUtils.urlDecode(nexturl));
return null;
} else {
@ -116,7 +113,7 @@ public class LoginController extends BaseController {
if (altUser != null && Application.getUserStore().existsUser(altUser)) {
loginSuccessed(request, response, altUser, true);
String nexturl = StringUtils.defaultIfBlank(request.getParameter("nexturl"), DEFAULT_HOME);
String nexturl = getParameter(request, "nexturl", homeUrl);
response.sendRedirect(CodecUtils.urlDecode(nexturl));
return null;
} else {
@ -136,9 +133,10 @@ public class LoginController extends BaseController {
ServletUtils.setSessionAttribute(request, SK_NEED_VCODE, true);
}
// H5 QR
String mobileQrUrl = RebuildConfiguration.getHomeUrl("/h5app/");
mobileQrUrl = AppUtils.getContextPath() + "/commons/barcode/render-qr?t=" + CodecUtils.urlEncode(mobileQrUrl);
// H5
String mobileUrl = RebuildConfiguration.getMobileUrl("/");
String mobileQrUrl = AppUtils.getContextPath() + "/commons/barcode/render-qr?t=" + CodecUtils.urlEncode(mobileUrl);
mv.getModel().put("mobileUrl", mobileUrl);
mv.getModel().put("mobileQrUrl", mobileQrUrl);
mv.getModelMap().put("UsersMsg", CheckDangers.getUsersDanger());
@ -190,35 +188,22 @@ public class LoginController extends BaseController {
return RespBody.ok(resMap);
}
/**
* @param user
* @param state
* @return
*/
private int getLoginRetryTimes(String user, int state) {
String key = "LoginRetry-" + user;
final String ckey = "LoginRetry-" + user;
if (state == -1) {
Application.getCommonsCache().evict(key);
Application.getCommonsCache().evict(ckey);
return 0;
}
Integer retry = (Integer) Application.getCommonsCache().getx(key);
Integer retry = (Integer) Application.getCommonsCache().getx(ckey);
retry = retry == null ? 0 : retry;
if (state == 1) {
retry += 1;
Application.getCommonsCache().putx(key, retry, CommonsCache.TS_HOUR);
Application.getCommonsCache().putx(ckey, retry, CommonsCache.TS_HOUR);
}
return retry;
}
/**
* 登录成功
*
* @param request
* @param response
* @param user
* @param autoLogin
*/
private void loginSuccessed(HttpServletRequest request, HttpServletResponse response, ID user, boolean autoLogin) {
// 自动登录
if (autoLogin) {
@ -237,16 +222,8 @@ public class LoginController extends BaseController {
Application.getSessionStore().storeLoginSuccessed(request);
}
/**
* 创建登陆日志
*
* @param request
* @param user
*/
protected void createLoginLog(HttpServletRequest request, ID user) {
String ipAddr = ServletUtils.getRemoteAddr(request);
private void createLoginLog(HttpServletRequest request, ID user) {
String UA = request.getHeader("user-agent");
try {
UserAgent uas = UserAgentUtil.parse(UA);
UA = String.format("%s-%s (%s)",
@ -260,7 +237,7 @@ public class LoginController extends BaseController {
Record record = EntityHelper.forNew(EntityHelper.LoginLog, UserService.SYSTEM_USER);
record.setID("user", user);
record.setString("ipAddr", ipAddr);
record.setString("ipAddr", ServletUtils.getRemoteAddr(request));
record.setString("userAgent", UA.toUpperCase());
record.setDate("loginTime", CalendarUtils.now());
Application.getCommonsService().create(record);

View file

@ -1260,7 +1260,7 @@
"短信签名":"短信签名",
"短信账户未配置或配置错误":"短信账户未配置或配置错误",
"确定":"确定",
"确定要禁用此用户吗?":"确定要禁用此用户吗?",
"确认要禁用此用户吗?":"确认要禁用此用户吗?",
"确认删除当前记录吗?":"确认删除当前记录吗?",
"确认删除此仪表盘?":"确认删除此仪表盘?",
"确认删除此任务?":"确认删除此任务?",

View file

@ -75,6 +75,14 @@
<index field-list="teamId,userId" type="unique"/>
</entity>
<entity name="ExternalUser" type-code="008" description="外部用户" queryable="false" parent="false">
<field name="userId" type="primary"/>
<field name="appUser" type="string" max-length="100" nullable="false" updatable="false"/>
<field name="appType" type="small-int" nullable="false" updatable="false" description="1=DingTalk,2=WxWork"/>
<field name="bindUser" type="reference" ref-entity="User" cascade="delete" nullable="false" updatable="false"/>
<index field-list="appType,appUser" type="unique"/>
</entity>
<entity name="MetaEntity" type-code="010" description="实体" name-field="entityName" queryable="false">
<field name="entityId" type="primary"/>
<field name="typeCode" type="small-int" nullable="false" updatable="false"/>

View file

@ -112,6 +112,16 @@ create table if not exists `team_member` (
unique index UIX0_team_member (`TEAM_ID`, `USER_ID`)
)Engine=InnoDB;
-- ************ Entity [ExternalUser] DDL ************
create table if not exists `external_user` (
`USER_ID` char(20) not null,
`APP_USER` varchar(100) not null,
`APP_TYPE` smallint(6) not null comment '1=DingTalk,2=WxWork',
`BIND_USER` char(20) not null,
primary key (`USER_ID`),
unique index UIX0_external_user (`APP_TYPE`, `APP_USER`)
)Engine=InnoDB;
-- ************ Entity [MetaEntity] DDL ************
create table if not exists `meta_entity` (
`ENTITY_ID` char(20) not null,
@ -790,4 +800,4 @@ insert into `project_plan_config` (`CONFIG_ID`, `PROJECT_ID`, `PLAN_NAME`, `SEQ`
-- DB Version (see `db-upgrade.sql`)
insert into `system_config` (`CONFIG_ID`, `ITEM`, `VALUE`)
values ('021-9000000000000001', 'DBVer', 36);
values ('021-9000000000000001', 'DBVer', 37);

View file

@ -1,6 +1,17 @@
-- Database upgrade scripts for rebuild 1.x and 2.x
-- Each upgraded starts with `-- #VERSION`
-- #37 (v2.5)
-- ************ Entity [ExternalUser] DDL ************
create table if not exists `external_user` (
`USER_ID` char(20) not null,
`APP_USER` varchar(100) not null,
`APP_TYPE` smallint(6) not null comment '1=DingTalk,2=WxWork',
`BIND_USER` char(20) not null,
primary key (`USER_ID`),
unique index UIX0_external_user (`APP_TYPE`, `APP_USER`)
)Engine=InnoDB;
-- #36 (v2.4)
-- ************ Entity [FrontjsCode] DDL ************
create table if not exists `frontjs_code` (

View file

@ -8,18 +8,28 @@
<ul class="sidebar-elements">
<li class="divider">[[${bundle.L('系统')}]]</li>
<li th:class="${active == 'systems'} ? 'active'">
<a th:href="@{/admin/systems}"><i class="icon zmdi zmdi-settings"></i><span>[[${bundle.L('通用配置')}]]</span></a>
<a th:href="@{/admin/systems}"><i class="icon zmdi zmdi-settings"></i>[[${bundle.L('通用配置')}]]</a>
</li>
<li class="parent">
<a><i class="icon zmdi zmdi-puzzle-piece"></i><span>[[${bundle.L('服务集成')}]]</span></a>
<a><i class="icon zmdi zmdi-puzzle-piece"></i>[[${bundle.L('服务集成')}]]</a>
<ul class="sub-menu">
<li class="title">[[${bundle.L('服务集成')}]]</li>
<li class="nav-items">
<div class="rb-scroller">
<div class="content">
<ul>
<li th:class="${active == 'integration-storage'} ? 'active'"><a th:href="@{/admin/integration/storage}">[[${bundle.L('云存储')}]]</a></li>
<li th:class="${active == 'integration-submail'} ? 'active'"><a th:href="@{/admin/integration/submail}">[[${bundle.L('邮件&短信')}]]</a></li>
<li th:class="${active == 'integration-storage'} ? 'active'">
<a th:href="@{/admin/integration/storage}">[[${bundle.L('云存储')}]]</a>
</li>
<li th:class="${active == 'integration-submail'} ? 'active'">
<a th:href="@{/admin/integration/submail}">[[${bundle.L('邮件&短信')}]]</a>
</li>
<li th:class="${active == 'integration-dingtalk'} ? 'active'">
<a th:href="@{/admin/integration/dingtalk}">[[${bundle.L('钉钉')}]] <sup class="rbv"></sup></a>
</li>
<li th:class="${active == 'integration-wxwork'} ? 'active'">
<a th:href="@{/admin/integration/wxwork}">[[${bundle.L('企业微信')}]] <sup class="rbv"></sup></a>
</li>
</ul>
</div>
</div>
@ -27,59 +37,59 @@
</ul>
</li>
<li th:class="${active == 'apis-manager'} ? 'active'">
<a th:href="@{/admin/apis-manager}"><i class="icon zmdi zmdi-key"></i><span>[[${bundle.L('API 秘钥')}]]</span></a>
<a th:href="@{/admin/apis-manager}"><i class="icon zmdi zmdi-key"></i>[[${bundle.L('API 秘钥')}]]</a>
</li>
<li class="divider">[[${bundle.L('业务实体')}]]</li>
<li th:class="${active == 'entities'} ? 'active'">
<a th:href="@{/admin/entities}"><i class="icon zmdi zmdi-widgets"></i><span>[[${bundle.L('实体管理')}]]</span></a>
<a th:href="@{/admin/entities}"><i class="icon zmdi zmdi-widgets"></i>[[${bundle.L('实体管理')}]]</a>
</li>
<li th:class="${active == 'classifications'} ? 'active'">
<a th:href="@{/admin/metadata/classifications}"><i class="icon zmdi zmdi-layers"></i><span>[[${bundle.L('分类数据')}]]</span></a>
<a th:href="@{/admin/metadata/classifications}"><i class="icon zmdi zmdi-layers"></i>[[${bundle.L('分类数据')}]]</a>
</li>
<li th:class="${active == 'robot-approval'} ? 'active'">
<a th:href="@{/admin/robot/approvals}"><i class="icon zmdi zmdi-assignment-check"></i><span>[[${bundle.L('审批流程')}]]</span></a>
<a th:href="@{/admin/robot/approvals}"><i class="icon zmdi zmdi-assignment-check"></i>[[${bundle.L('审批流程')}]]</a>
</li>
<li th:class="${active == 'transforms'} ? 'active'">
<a th:href="@{/admin/transforms}"><i class="icon zmdi zmdi-transform"></i><span>[[${bundle.L('记录转换映射')}]]</span></a>
<a th:href="@{/admin/transforms}"><i class="icon zmdi zmdi-transform"></i>[[${bundle.L('记录转换映射')}]]</a>
</li>
<li th:class="${active == 'robot-trigger'} ? 'active'">
<a th:href="@{/admin/robot/triggers}"><i class="icon zmdi zmdi-rotate-cw"></i><span>[[${bundle.L('触发器')}]]</span></a>
<a th:href="@{/admin/robot/triggers}"><i class="icon zmdi zmdi-rotate-cw"></i>[[${bundle.L('触发器')}]]</a>
</li>
<li th:class="${active == 'extforms'} ? 'active'">
<a th:href="@{/admin/extforms}"><i class="icon zmdi zmdi-collection-text"></i><span>[[${bundle.L('外部表单')}]]</span> <sup class="rbv"></sup></a>
<a th:href="@{/admin/extforms}"><i class="icon zmdi zmdi-collection-text"></i>[[${bundle.L('外部表单')}]] <sup class="rbv"></sup> </a>
</li>
<li th:class="${active == 'data-imports'} ? 'active'">
<a th:href="@{/admin/data/data-imports}"><i class="icon zmdi zmdi-cloud-upload"></i><span>[[${bundle.L('数据导入')}]]</span></a>
<a th:href="@{/admin/data/data-imports}"><i class="icon zmdi zmdi-cloud-upload"></i>[[${bundle.L('数据导入')}]]</a>
</li>
<li th:class="${active == 'report-templates'} ? 'active'">
<a th:href="@{/admin/data/report-templates}"><i class="icon zmdi zmdi-map"></i><span>[[${bundle.L('报表模板')}]]</span></a>
<a th:href="@{/admin/data/report-templates}"><i class="icon zmdi zmdi-map"></i>[[${bundle.L('报表模板')}]]</a>
</li>
<li class="divider">[[${bundle.L('高级功能')}]]</li>
<li th:class="${active == 'projects'} ? 'active'">
<a th:href="@{/admin/projects}"><i class="icon zmdi zmdi-shape"></i><span>[[${bundle.L('项目')}]]</span></a>
<a th:href="@{/admin/projects}"><i class="icon zmdi zmdi-shape"></i>[[${bundle.L('项目')}]]</a>
</li>
<li th:class="${active == 'frontjs-code'} ? 'active'">
<a th:href="@{/admin/frontjs-code}"><i class="icon zmdi zmdi-code"></i><span>FrontJS</span> <sup class="rbv"></sup></a>
<a th:href="@{/admin/frontjs-code}"><i class="icon zmdi zmdi-code"></i>FrontJS <sup class="rbv"></sup></a>
</li>
<li class="divider">[[${bundle.L('用户')}]]</li>
<li th:class="${active == 'users'} ? 'active'">
<a th:href="@{/admin/bizuser/users}"><i class="icon zmdi zmdi-accounts"></i><span>[[${bundle.L('部门用户')}]]</span></a>
<a th:href="@{/admin/bizuser/users}"><i class="icon zmdi zmdi-accounts"></i>[[${bundle.L('部门用户')}]]</a>
</li>
<li th:class="${active == 'role-privileges'} ? 'active'">
<a th:href="@{/admin/bizuser/role-privileges}"><i class="icon zmdi zmdi-lock"></i><span>[[${bundle.L('角色权限')}]]</span></a>
<a th:href="@{/admin/bizuser/role-privileges}"><i class="icon zmdi zmdi-lock"></i>[[${bundle.L('角色权限')}]]</a>
</li>
<li th:class="${active == 'teams'} ? 'active'">
<a th:href="@{/admin/bizuser/teams}"><i class="icon zmdi zmdi-case"></i><span>[[${bundle.L('团队')}]]</span></a>
<a th:href="@{/admin/bizuser/teams}"><i class="icon zmdi zmdi-case"></i>[[${bundle.L('团队')}]]</a>
</li>
<li class="divider">[[${bundle.L('审计')}]]</li>
<li th:class="${active == 'login-logs'} ? 'active'">
<a th:href="@{/admin/audit/login-logs}"><i class="icon zmdi zmdi-pin-account"></i><span>[[${bundle.L('登录日志')}]]</span></a>
<a th:href="@{/admin/audit/login-logs}"><i class="icon zmdi zmdi-pin-account"></i>[[${bundle.L('登录日志')}]]</a>
</li>
<li th:class="${active == 'revision-history'} ? 'active'">
<a th:href="@{/admin/audit/revision-history}"><i class="icon zmdi zmdi-wrap-text"></i><span>[[${bundle.L('变更历史')}]]</span></a>
<a th:href="@{/admin/audit/revision-history}"><i class="icon zmdi zmdi-wrap-text"></i>[[${bundle.L('变更历史')}]]</a>
</li>
<li th:class="${active == 'recycle-bin'} ? 'active'">
<a th:href="@{/admin/audit/recycle-bin}"><i class="icon zmdi zmdi-delete fs-16"></i><span>[[${bundle.L('回收站')}]]</span></a>
<a th:href="@{/admin/audit/recycle-bin}"><i class="icon zmdi zmdi-delete fs-16"></i>[[${bundle.L('回收站')}]]</a>
</li>
</ul>
</div>

View file

@ -6,7 +6,7 @@
<style type="text/css">
.rb-navbar-header .rb-toggle-left-sidebar,
.rb-icons-nav {
display: none !important
display: none !important;
}
</style>
</head>

View file

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:replace="~{/_include/header}" />
<meta name="page-help" content="https://getrebuild.com/docs/admin/integration-dingtalk" />
<title>[[${bundle.L('钉钉')}]]</title>
</head>
<body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header" th:classappend="${sideCollapsedClazz}">
<th:block th:replace="~{/_include/nav-top}" />
<th:block th:replace="~{/_include/nav-left-admin(active='integration-dingtalk')}" />
<div class="rb-content">
<div class="main-content container-fluid syscfg">
<div class="row">
<div class="col-lg-9 col-12">
<div class="card">
<div class="card-header pb-1">
[[${bundle.L('钉钉')}]]
<a href="#modfiy" class="float-right"><i class="icon zmdi zmdi-edit"></i> [[${bundle.L('修改')}]]</a>
</div>
<div class="card-body">
<h5>[[${bundle.L('接口凭证')}]]</h5>
<table class="table">
<tbody>
<tr>
<td width="40%">AgentId</td>
<td data-id="DingtalkAgentid" th:data-value="${DingtalkAgentid ?:''}">[[${DingtalkAgentid ?:bundle.L('未设置')}]]</td>
</tr>
<tr>
<td>AppKey</td>
<td data-id="DingtalkAppkey" th:data-value="${DingtalkAppkey ?:''}">[[${DingtalkAppkey ?:bundle.L('未设置')}]]</td>
</tr>
<tr>
<td>AppSecret</td>
<td data-id="DingtalkAppsecret" th:data-value="${DingtalkAppsecret ?:''}">[[${DingtalkAppsecret ?:bundle.L('未设置')}]]</td>
</tr>
<tr>
<td>CorpId</td>
<td data-id="DingtalkCorpid" th:data-value="${DingtalkCorpid ?:''}">[[${DingtalkCorpid ?:bundle.L('未设置')}]]</td>
</tr>
</tbody>
</table>
<h5>[[${bundle.L('钉钉侧配置')}]]</h5>
<table class="table">
<tbody>
<tr>
<td width="40%">[[${bundle.L('应用首页地址')}]]</td>
<td>[[${_DingtalkHomeUrl}]]</td>
</tr>
</tbody>
</table>
<div class="edit-footer">
<button class="btn btn-link">[[${bundle.L('取消')}]]</button>
<button class="btn btn-primary">[[${bundle.L('保存')}]]</button>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-12"></div>
</div>
</div>
</div>
</div>
<th:block th:replace="~{/_include/footer}" />
<script th:src="@{/assets/js/admin/syscfg.js}" type="text/babel"></script>
</body>
</html>

View file

@ -24,22 +24,30 @@
<tbody>
<tr>
<td width="40%">[[${bundle.L('访问域名')}]]</td>
<td data-id="StorageURL" th:data-value="${storageAccount == null ? '' : storageAccount[3]}">[[${storageAccount == null ? bundle.L('未设置') : storageAccount[3]}]]</td>
<td data-id="StorageURL" th:data-value="${storageAccount == null ? '' : storageAccount[3]}">
[[${storageAccount == null ? bundle.L('未设置') : storageAccount[3]}]]
</td>
</tr>
<tr>
<td>
[[${bundle.L('存储空间')}]]
<p>[[${bundle.L('存储空间变更需你自行迁移原有数据')}]]</p>
</td>
<td data-id="StorageBucket" th:data-value="${storageAccount == null ? '' : storageAccount[2]}">[[${storageAccount == null ? bundle.L('未设置') : storageAccount[2]}]]</td>
<td data-id="StorageBucket" th:data-value="${storageAccount == null ? '' : storageAccount[2]}">
[[${storageAccount == null ? bundle.L('未设置') : storageAccount[2]}]]
</td>
</tr>
<tr>
<td>[[${bundle.L('秘钥 AK')}]]</td>
<td data-id="StorageApiKey" th:data-value="${storageAccount == null ? '' : storageAccount[0]}">[[${storageAccount == null ? bundle.L('未设置') : storageAccount[0]}]]</td>
<td data-id="StorageApiKey" th:data-value="${storageAccount == null ? '' : storageAccount[0]}">
[[${storageAccount == null ? bundle.L('未设置') : storageAccount[0]}]]
</td>
</tr>
<tr>
<td>[[${bundle.L('秘钥 SK')}]]</td>
<td data-id="StorageApiSecret" th:data-value="${storageAccount == null ? '' : storageAccount[1]}">[[${storageAccount == null ? bundle.L('未设置') : storageAccount[1]}]]</td>
<td data-id="StorageApiSecret" th:data-value="${storageAccount == null ? '' : storageAccount[1]}">
[[${storageAccount == null ? bundle.L('未设置') : storageAccount[1]}]]
</td>
</tr>
</tbody>
</table>

View file

@ -54,25 +54,33 @@
<span class="smtp-hide">APPID</span>
<span class="smtp-show">[[${bundle.L('SMTP 用户名')}]]</span>
</td>
<td data-id="MailUser" th:data-value="${mailAccount == null ? '' : mailAccount[0]}">[[${mailAccount == null ? bundle.L('未设置') : mailAccount[0]}]]</td>
<td data-id="MailUser" th:data-value="${mailAccount == null ? '' : mailAccount[0]}">
[[${mailAccount == null ? bundle.L('未设置') : mailAccount[0]}]]
</td>
</tr>
<tr>
<td>
<span class="smtp-hide">APPKEY</span>
<span class="smtp-show">[[${bundle.L('SMTP 密码')}]]</span>
</td>
<td data-id="MailPassword" th:data-value="${mailAccount == null ? '' : mailAccount[1]}">[[${mailAccount == null ? bundle.L('未设置') : mailAccount[1]}]]</td>
<td data-id="MailPassword" th:data-value="${mailAccount == null ? '' : mailAccount[1]}">
[[${mailAccount == null ? bundle.L('未设置') : mailAccount[1]}]]
</td>
</tr>
<tr>
<td>
[[${bundle.L('发件人地址')}]]
<p class="smtp-hide">[[${bundle.L('地址域名需与 SUBMAIL 中配置的域名匹配')}]]</p>
</td>
<td data-id="MailAddr" th:data-value="${mailAccount == null ? '' : mailAccount[2]}">[[${mailAccount == null ? bundle.L('未设置') : mailAccount[2]}]]</td>
<td data-id="MailAddr" th:data-value="${mailAccount == null ? '' : mailAccount[2]}">
[[${mailAccount == null ? bundle.L('未设置') : mailAccount[2]}]]
</td>
</tr>
<tr>
<td>[[${bundle.L('发件人名称')}]]</td>
<td data-id="MailName" th:data-value="${mailAccount == null ? '' : mailAccount[3]}">[[${mailAccount == null ? bundle.L('未设置') : mailAccount[3]}]]</td>
<td data-id="MailName" th:data-value="${mailAccount == null ? '' : mailAccount[3]}">
[[${mailAccount == null ? bundle.L('未设置') : mailAccount[3]}]]
</td>
</tr>
<tr class="show-on-edit">
<td></td>
@ -93,15 +101,21 @@
<tbody>
<tr>
<td width="40%">APPID</td>
<td data-id="SmsUser" th:data-value="${smsAccount == null ? '' : smsAccount[0]}">[[${smsAccount == null ? bundle.L('未设置') : smsAccount[0]}]]</td>
<td data-id="SmsUser" th:data-value="${smsAccount == null ? '' : smsAccount[0]}">
[[${smsAccount == null ? bundle.L('未设置') : smsAccount[0]}]]
</td>
</tr>
<tr>
<td>APPKEY</td>
<td data-id="SmsPassword" th:data-value="${smsAccount == null ? '' : smsAccount[1]}">[[${smsAccount == null ? bundle.L('未设置') : smsAccount[1]}]]</td>
<td data-id="SmsPassword" th:data-value="${smsAccount == null ? '' : smsAccount[1]}">
[[${smsAccount == null ? bundle.L('未设置') : smsAccount[1]}]]
</td>
</tr>
<tr>
<td>[[${bundle.L('短信签名')}]]</td>
<td data-id="SmsSign" th:data-value="${smsAccount == null ? '' : smsAccount[2]}">[[${smsAccount == null ? bundle.L('未设置') : smsAccount[2]}]]</td>
<td data-id="SmsSign" th:data-value="${smsAccount == null ? '' : smsAccount[2]}">
[[${smsAccount == null ? bundle.L('未设置') : smsAccount[2]}]]
</td>
</tr>
<tr class="show-on-edit">
<td></td>

View file

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:replace="~{/_include/header}" />
<meta name="page-help" content="https://getrebuild.com/docs/admin/integration-wxwork" />
<title>[[${bundle.L('企业微信')}]]</title>
</head>
<body>
<div class="rb-wrapper rb-fixed-sidebar rb-collapsible-sidebar rb-collapsible-sidebar-hide-logo rb-color-header" th:classappend="${sideCollapsedClazz}">
<th:block th:replace="~{/_include/nav-top}" />
<th:block th:replace="~{/_include/nav-left-admin(active='integration-wxwork')}" />
<div class="rb-content">
<div class="main-content container-fluid syscfg">
<div class="row">
<div class="col-lg-9 col-12">
<div class="card">
<div class="card-header pb-1">
[[${bundle.L('企业微信')}]]
<a href="#modfiy" class="float-right"><i class="icon zmdi zmdi-edit"></i> [[${bundle.L('修改')}]]</a>
</div>
<div class="card-body">
<h5>[[${bundle.L('接口凭证')}]]</h5>
<table class="table">
<tbody>
<tr>
<td width="40%">AgentId</td>
<td data-id="WxworkAgentid" th:data-value="${WxworkAgentid ?:''}">[[${WxworkAgentid ?:bundle.L('未设置')}]]</td>
</tr>
<tr>
<td>Secret</td>
<td data-id="WxworkSecret" th:data-value="${WxworkSecret ?:''}">[[${WxworkSecret ?:bundle.L('未设置')}]]</td>
</tr>
<tr>
<td>[[${bundle.L('企业 ID')}]]</td>
<td data-id="WxworkCorpid" th:data-value="${WxworkCorpid ?:''}">[[${WxworkCorpid ?:bundle.L('未设置')}]]</td>
</tr>
</tbody>
</table>
<h5>[[${bundle.L('企业微信侧配置')}]]</h5>
<table class="table">
<tbody>
<tr>
<td width="40%">[[${bundle.L('应用主页')}]]</td>
<td>[[${_WxworkHomeUrl}]]</td>
</tr>
<tr>
<td>[[${bundle.L('可信域名')}]]</td>
<td>[[${_WxworkAuthCallUrl}]]</td>
</tr>
</tbody>
</table>
<div class="edit-footer">
<button class="btn btn-link">[[${bundle.L('取消')}]]</button>
<button class="btn btn-primary">[[${bundle.L('保存')}]]</button>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-12"></div>
</div>
</div>
</div>
</div>
<th:block th:replace="~{/_include/footer}" />
<script th:src="@{/assets/js/admin/syscfg.js}" type="text/babel"></script>
</body>
</html>

View file

@ -11542,7 +11542,7 @@ canvas {
.rb-offcanvas-menu .rb-top-header .rb-navbar-header {
display: block;
width: auto;
background-color: #4285f4;
/*background-color: #4285f4;*/
}
}

View file

@ -4100,4 +4100,43 @@ body.fullscreen .rb-wrapper > .rb-content {
.card-header {
font-weight: normal;
}
}
html.external-auth,
html.external-auth body {
height: auto;
background-color: #fff;
}
html.external-auth .auth-body {
max-width: 421px;
margin: 0 auto;
}
html.external-auth .auth-body .alogo {
margin-top: 61px;
margin-bottom: 51px;
}
html.external-auth .auth-body .alogo img {
display: inline-block;
width: 64px;
height: 64px;
border-radius: 14px;
}
html.external-auth .auth-body .alogo .zmdi {
font-size: 3rem;
margin-top: 12px;
}
html.external-auth .auth-body .card {
margin-left: 20px;
margin-right: 20px;
padding-left: 20px;
padding-right: 20px;
}
html.external-auth .auth-body.must-center .card {
display: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View file

@ -35,7 +35,7 @@ $(document).ready(function () {
})
$('.J_disable').click(() => {
RbAlert.create($L('要禁用此用户吗'), {
RbAlert.create($L('要禁用此用户吗'), {
confirmText: $L('禁用'),
confirm: function () {
toggleDisabled(true, this)

View file

@ -50,6 +50,22 @@ $(document).ready(function () {
})
})
const $unauth = $('.J_unauth-dingtalk, .J_unauth-wxwork').on('click', () => {
RbAlert.create($L('确认要取消授权吗'), {
confirm: function () {
this.hide()
$.post(`/settings/cancel-external-user?type=${$unauth.data('type')}`, (res) => {
if (res.error_code === 0) {
location.hash = 'secure'
location.reload()
} else {
RbHighbar.create(res.error_msg)
}
})
},
})
})
// load log
$('a.nav-link[href="#logs"]').click(() => {

View file

@ -28,6 +28,11 @@
.error-container a[target='_blank']:hover {
text-decoration: underline;
}
@media (max-width: 576px) {
.rb-error .error-container {
max-width: 100%;
}
}
</style>
</head>
<body class="rb-splash-screen">

View file

@ -99,20 +99,20 @@
<div class="col-md-8 col-12">
<form>
<div class="form-group row">
<label class="col-sm-4 col-form-label text-left">[[${bundle.L('用户名')}]]</label>
<div class="col-sm-8">
<label class="col-4 col-form-label">[[${bundle.L('用户名')}]]</label>
<div class="col-8">
<div class="form-control-plaintext" th:text="${user.getName()}"></div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label text-left">[[${bundle.L('所属部门')}]]</label>
<div class="col-sm-8">
<label class="col-4 col-form-label">[[${bundle.L('所属部门')}]]</label>
<div class="col-8">
<div class="form-control-plaintext" th:text="${user.getOwningBizUnit().getName()}"></div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label text-left">[[${bundle.L('加入团队')}]]</label>
<div class="col-sm-8">
<label class="col-4 col-form-label">[[${bundle.L('加入团队')}]]</label>
<div class="col-8">
<div class="form-control-plaintext split-span">
<th:block th:each="team : ${user.getOwningTeams()}">
<span th:text="${team.getName()}"></span>
@ -122,14 +122,20 @@
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label text-left">[[${bundle.L('姓名')}]]</label>
<div class="col-sm-8">
<input class="form-control form-control-sm" type="text" id="fullName" th:value="${user.getFullName()}" th:data-o="${user.getFullName()}" />
<label class="col-4 col-form-label">[[${bundle.L('姓名')}]]</label>
<div class="col-8">
<input
class="form-control form-control-sm"
type="text"
id="fullName"
th:value="${user.getFullName()}"
th:data-o="${user.getFullName()}"
/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label text-left">[[${bundle.L('工作电话')}]]</label>
<div class="col-sm-8">
<label class="col-4 col-form-label">[[${bundle.L('工作电话')}]]</label>
<div class="col-8">
<input
class="form-control form-control-sm"
type="text"
@ -141,7 +147,7 @@
</div>
</div>
<div class="form-group row border-none mt-3">
<div class="col-sm-8 offset-sm-4">
<div class="col-8 offset-sm-4">
<button class="btn btn-primary J_save" type="button">[[${bundle.L('确定')}]]</button>
</div>
</div>
@ -152,23 +158,41 @@
<div class="tab-pane" id="secure">
<form>
<div class="form-group row">
<label class="col-sm-2 col-form-label text-left">[[${bundle.L('修改邮箱')}]]</label>
<div class="col-sm-7 pl-0">
<label class="col-2 col-form-label">[[${bundle.L('修改邮箱')}]]</label>
<div class="col-7 pl-0">
<div class="form-control-plaintext text-muted J_email-account" th:text="${user.getEmail() ?: bundle.L('邮箱未设置')}"></div>
</div>
<div class="col-sm-3 text-right">
<div class="col-3 text-right">
<button class="btn btn-primary btn-outline J_email" type="button">[[${bundle.L('修改')}]]</button>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label text-left">[[${bundle.L('修改密码')}]]</label>
<div class="col-sm-7 pl-0">
<label class="col-2 col-form-label">[[${bundle.L('修改密码')}]]</label>
<div class="col-7 pl-0">
<div class="form-control-plaintext text-muted">[[${bundle.L('建议 90 天更改一次密码')}]]</div>
</div>
<div class="col-sm-3 text-right">
<div class="col-3 text-right">
<button class="btn btn-primary btn-outline J_passwd" type="button">[[${bundle.L('修改')}]]</button>
</div>
</div>
<div th:if="${dingtalkUser != null}" class="form-group row">
<label class="col-2 col-form-label">[[${bundle.L('钉钉授权')}]]</label>
<div class="col-7 pl-0">
<div class="form-control-plaintext text-muted">[[${dingtalkUser}]]</div>
</div>
<div class="col-3 text-right">
<button class="btn btn-danger btn-outline J_unauth-dingtalk" type="button" data-type="1">[[${bundle.L('取消授权')}]]</button>
</div>
</div>
<div th:if="${wxworkUser != null}" class="form-group row">
<label class="col-2 col-form-label">[[${bundle.L('企业微信授权')}]]</label>
<div class="col-7 pl-0">
<div class="form-control-plaintext text-muted">[[${wxworkUser}]]</div>
</div>
<div class="col-3 text-right">
<button class="btn btn-danger btn-outline J_unauth-wxwork" type="button" data-type="2">[[${bundle.L('取消授权')}]]</button>
</div>
</div>
</form>
</div>
<div class="tab-pane" id="logs">

View file

@ -42,7 +42,7 @@
z-index: 1;
background: rgba(0, 0, 0, 0.1);
}
.o-platform a > i.zmdi {
.o-platform a > .icon {
background-color: #fc9a00;
color: #fff;
border-radius: 50%;
@ -50,7 +50,7 @@
height: 25px;
line-height: 26px;
font-size: 1.2rem;
padding-left: 9px;
text-align: center;
}
.o-platform a > span {
color: #666;
@ -62,6 +62,10 @@
width: 160px;
padding: 6px;
}
.o-platform:hover .dropdown-menu {
display: block;
margin-top: 0; /* remove the gap so it doesn't close */
}
</style>
</head>
<body class="rb-splash-screen">
@ -112,7 +116,7 @@
<div class="row mb-2">
<div class="col" th:if="${mobileQrUrl != null}">
<div class="btn-group dropup o-platform">
<a class="hover-opacity dropdown-toggle" data-toggle="dropdown">
<a class="hover-opacity dropdown-toggle" data-toggle="dropdown" th:href="${mobileUrl}">
<i class="icon zmdi zmdi-smartphone-iphone"></i> <span class="up-1">[[${bundle.L('手机版')}]]</span>
</a>
<div class="dropdown-menu">
@ -154,10 +158,11 @@
return
}
$('.o-platform .dropdown-toggle').on('mouseenter', function () {
$(this).trigger('click')
// $(this).dropdown('show')
})
if ($.browser.mobile) {
$(`<div class="bg-info"><i class="icon zmdi zmdi-smartphone-iphone"></i><p>${$L('点击切换到手机版访问')}</p></div>`)
.appendTo('.announcement-wrapper')
.on('click', () => (location.href = $('.o-platform .dropdown-toggle').attr('href')))
}
$('.vcode-row img').on('click', function () {
$(this).attr('src', 'captcha?' + $random())

View file

@ -18,14 +18,14 @@ import org.junit.jupiter.api.Test;
public class NotificationServiceTest extends TestSupport {
@Test
public void testSend() {
Message msg = MessageBuilder.createMessage(SIMPLE_USER, "发一条消息", null);
void testSend() {
Message msg = MessageBuilder.createMessage(SIMPLE_USER, "发一条消息");
Application.getNotifications().send(msg);
System.out.println("Notification Sent");
}
@Test
public void testGetUnread() {
void testGetUnread() {
Application.getNotifications().getUnreadMessage(SIMPLE_USER);
}
}