* be: tips

* enh: h5NoKill

* enh: session  Keep 60s for H5

* enh: H5 session setMaxInactiveInterval

* ImageMaker

---------

Co-authored-by: devezhao <zhaofang123@gmail.com>
This commit is contained in:
REBUILD 企业管理系统 2024-01-08 14:27:59 +08:00 committed by GitHub
parent d47dfb007f
commit d68cce0673
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 232 additions and 117 deletions

View file

@ -24,21 +24,13 @@ import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.core.service.approval.FlowNode;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.utils.ImageMaker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.RandomUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
@ -305,95 +297,28 @@ public class UserHelper {
return users;
}
private static final Color[] RB_COLORS = new Color[]{
new Color(66, 133, 244),
new Color(52, 168, 83),
new Color(251, 188, 5),
new Color(234, 67, 53),
new Color(155, 82, 222),
new Color(22, 168, 143),
};
/**
* 生成用户头像
*
* @param name
* @param forceMake
* @return
* @see ImageMaker
*/
public static File generateAvatar(String name, boolean forceMake) {
if (StringUtils.isBlank(name)) name = "RB";
File avatar = RebuildConfiguration.getFileOfData("avatar-" + name + "29.jpg");
if (avatar.exists()) {
File avatarFile = RebuildConfiguration.getFileOfData("avatar-" + name + "29.jpg");
if (avatarFile.exists()) {
if (forceMake) {
FileUtils.deleteQuietly(avatar);
FileUtils.deleteQuietly(avatarFile);
} else {
return avatar;
return avatarFile;
}
}
if (name.length() > 2) name = name.substring(name.length() - 2);
name = name.toUpperCase();
BufferedImage bi = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = (Graphics2D) bi.getGraphics();
g2d.setColor(RB_COLORS[RandomUtils.nextInt(RB_COLORS.length)]);
g2d.fillRect(0, 0, bi.getWidth(), bi.getHeight());
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
try {
final Font font = createFont();
g2d.setFont(font);
g2d.setColor(Color.WHITE);
FontMetrics fontMetrics = g2d.getFontMetrics(font);
int x = fontMetrics.stringWidth(name);
g2d.drawString(name, (200 - x) / 2, 128);
g2d.setColor(new Color(0, 0, 0, 1));
g2d.drawString("wbr", 0, 62);
g2d.dispose();
try (FileOutputStream fos = new FileOutputStream(avatar)) {
ImageIO.write(bi, "png", fos);
fos.flush();
}
} catch (Throwable ex) {
log.warn("Cannot make font-avatar : {}", name, ex);
InputStream is = null;
try {
is = CommonsUtils.getStreamOfRes("/web" + DEFAULT_AVATAR);
bi = ImageIO.read(is);
try (FileOutputStream fos = new FileOutputStream(avatar)) {
ImageIO.write(bi, "png", fos);
fos.flush();
}
} catch (IOException ignored) {
IOUtils.closeQuietly(is);
}
}
return avatar;
}
private static Font createFont() {
File fontFile = RebuildConfiguration.getFileOfData("SourceHanSansK-Regular.ttf");
if (fontFile.exists()) {
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
font = font.deriveFont((float) 81.0);
return font;
} catch (Throwable ex) {
log.warn("Cannot create Font: SourceHanSansK-Regular.ttf", ex);
}
}
// Use default
return new Font(Font.SERIF, Font.BOLD, (int) (float) 81.0);
ImageMaker.makeAvatar(name, avatarFile);
return avatarFile;
}
/**

View file

@ -0,0 +1,145 @@
package com.rebuild.utils;
import com.rebuild.core.RebuildException;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.support.RebuildConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.math.RandomUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Zixin
* @since 2023/1/8
*/
@Slf4j
public class ImageMaker {
// 颜色
public static final Color[] RB_COLORS = new Color[]{
new Color(66, 133, 244),
new Color(52, 168, 83),
new Color(251, 188, 5),
new Color(234, 67, 53),
new Color(155, 82, 222),
new Color(22, 168, 143),
};
/**
* 生成LOGO效果不佳暂不用
*
* @param text
* @param color
* @param dest
* @return
*/
@Deprecated
public static void makeLogo(String text, Color color, File dest) {
BufferedImage bi = new BufferedImage(300, 60, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) bi.getGraphics();
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect(0, 0, bi.getWidth(), bi.getHeight());
g2d.setComposite(AlphaComposite.SrcOver);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Color textColor = color == null ? RB_COLORS[RandomUtils.nextInt(RB_COLORS.length)] : color;
FileUtils.deleteQuietly(dest);
try {
final Font font = createFont(63f);
g2d.setFont(font);
g2d.setColor(textColor);
FontMetrics fontMetrics = g2d.getFontMetrics(font);
int x = fontMetrics.stringWidth(text);
g2d.drawString(text, (300 - x) / 2, 60 - 6);
try (FileOutputStream fos = new FileOutputStream(dest)) {
ImageIO.write(bi, "png", fos);
fos.flush();
}
} catch (Throwable ex) {
throw new RebuildException("Cannot make logo", ex);
}
}
/**
* 生成头像
*
* @param name
* @param dest
*/
public static void makeAvatar(String name, File dest) {
if (name.length() > 2) name = name.substring(name.length() - 2);
name = name.toUpperCase();
BufferedImage bi = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = (Graphics2D) bi.getGraphics();
g2d.setColor(RB_COLORS[RandomUtils.nextInt(RB_COLORS.length)]);
g2d.fillRect(0, 0, bi.getWidth(), bi.getHeight());
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
FileUtils.deleteQuietly(dest);
try {
final Font font = createFont(81f);
g2d.setFont(font);
g2d.setColor(Color.WHITE);
FontMetrics fontMetrics = g2d.getFontMetrics(font);
int x = fontMetrics.stringWidth(name);
g2d.drawString(name, (200 - x) / 2, 128);
g2d.setColor(new Color(0, 0, 0, 1));
g2d.drawString("wbr", 0, 62);
g2d.dispose();
try (FileOutputStream fos = new FileOutputStream(dest)) {
ImageIO.write(bi, "png", fos);
fos.flush();
}
} catch (Throwable ex) {
log.warn("Cannot make font-avatar : {}", name, ex);
InputStream is = null;
try {
is = CommonsUtils.getStreamOfRes("/web" + UserHelper.DEFAULT_AVATAR);
bi = ImageIO.read(is);
try (FileOutputStream fos = new FileOutputStream(dest)) {
ImageIO.write(bi, "png", fos);
fos.flush();
}
} catch (IOException ignored) {
IOUtils.closeQuietly(is);
}
}
}
/**
* 获取字体
*
* @return
*/
static Font createFont(float size) {
File fontFile = RebuildConfiguration.getFileOfData("SourceHanSansK-Regular.ttf");
if (fontFile.exists()) {
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
font = font.deriveFont(size);
return font;
} catch (Throwable ex) {
log.warn("Cannot create Font: SourceHanSansK-Regular.ttf", ex);
}
}
// Use default
return new Font(Font.SERIF, Font.BOLD, (int) size);
}
}

View file

@ -107,14 +107,15 @@ public class OnlineSessionStore implements HttpSessionListener {
/**
* @param request
* @param h5NoKill
*/
public void storeLoginSuccessed(HttpServletRequest request) {
public void storeLoginSuccessed(HttpServletRequest request, boolean h5NoKill) {
HttpSession s = request.getSession();
Object loginUser = s.getAttribute(WebUtils.CURRENT_USER);
Assert.notNull(loginUser, "No login user found in session!");
if (!RebuildConfiguration.getBool(ConfigurationItem.MultipleSessions)) {
HttpSession previous = getSession((ID) loginUser);
HttpSession previous = h5NoKill ? null : getSession((ID) loginUser);
if (previous != null) {
log.warn("Kill previous session : {} ({})", previous.getId(), loginUser);

View file

@ -176,7 +176,10 @@ public class RebuildWebInterceptor implements AsyncHandlerInterceptor, InstallSt
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// Notings
// v3.6 H5 session 时间
if (AppUtils.isRbMobile(request)) {
request.getSession().setMaxInactiveInterval(60 * 5);
}
}
@Override

View file

@ -59,6 +59,36 @@ public class LoginAction extends BaseController {
protected static final String PREFIX_2FA = "2FA:";
protected static final String PREFIX_ALT = "ALT:";
/**
* @param request
* @param response
* @param user
* @param autoLogin
* @return
*/
protected Integer loginSuccessed(HttpServletRequest request, HttpServletResponse response, ID user, boolean autoLogin) {
return loginSuccessed(request, response, user, autoLogin, false);
}
/**
* @param request
* @param response
* @param user
* @return
*/
protected Map<String, Object> loginSuccessedH5(HttpServletRequest request, HttpServletResponse response, ID user) {
Map<String, Object> resMap = new HashMap<>();
Integer ed = loginSuccessed(request, response, user, false, true);
if (ed != null) resMap.put("passwdExpiredDays", ed);
String authToken = AuthTokenManager.generateAccessToken(user);
resMap.put("authToken", authToken);
request.getSession().invalidate();
return resMap;
}
/**
* 登录成功
*
@ -66,9 +96,10 @@ public class LoginAction extends BaseController {
* @param response
* @param user
* @param autoLogin
* @param fromH5
* @return 密码过期时间如有
*/
protected Integer loginSuccessed(HttpServletRequest request, HttpServletResponse response, ID user, boolean autoLogin) {
private Integer loginSuccessed(HttpServletRequest request, HttpServletResponse response, ID user, boolean autoLogin, boolean fromH5) {
// 自动登录
if (autoLogin) {
final String altToken = CodecUtils.randomCode(60);
@ -82,7 +113,7 @@ public class LoginAction extends BaseController {
ServletUtils.setSessionAttribute(request, WebUtils.CURRENT_USER, user);
ServletUtils.setSessionAttribute(request, SK_USER_THEME, KVStorage.getCustomValue("THEME." + user));
Application.getSessionStore().storeLoginSuccessed(request);
Application.getSessionStore().storeLoginSuccessed(request, fromH5);
// 头像缓存
ServletUtils.setSessionAttribute(request, UserAvatar.SK_DAVATAR, System.currentTimeMillis());
@ -109,27 +140,8 @@ public class LoginAction extends BaseController {
}
/**
* 登录成功 H5
* 登录日志
*
* @param request
* @param response
* @param user
* @return
*/
protected Map<String, Object> loginSuccessedH5(HttpServletRequest request, HttpServletResponse response, ID user) {
Map<String, Object> resMap = new HashMap<>();
Integer ed = loginSuccessed(request, response, user, false);
if (ed != null) resMap.put("passwdExpiredDays", ed);
String authToken = AuthTokenManager.generateAccessToken(user);
resMap.put("authToken", authToken);
request.getSession().invalidate();
return resMap;
}
/**
* @param request
* @param user
*/

View file

@ -28,11 +28,10 @@
<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>[[${bundle.L('存储空间')}]]</td>
<td data-id="StorageBucket" th:data-value="${storageAccount == null ? '' : storageAccount[2]}" th:data-form-text="${bundle.L('存储空间变更需你自行迁移原有数据')}">
[[${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>

View file

@ -21,6 +21,9 @@
.smtp tr.smtp-show {
display: table-row;
}
.smtp td[data-id='MailAddr'] > p {
display: none;
}
</style>
</head>
<body>
@ -62,11 +65,10 @@
<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>[[${bundle.L('发件人地址')}]]</td>
<td data-id="MailAddr" th:data-value="${mailAccount == null ? '' : mailAccount[2]}" th:data-form-text="${bundle.L('地址域名需与 SUBMAIL 中配置的域名匹配')}">
[[${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>

View file

@ -105,7 +105,7 @@
<i class="logo-img white"></i>
<b th:title="${bundle.L('还原')}"><span class="zmdi zmdi-replay"></span></b>
</a>
<p class="mt-2 text-dark hide">[[${bundle.L('请分别上传深色与白色 LOGO透明背景建议尺寸 300 × 60')}]]</p>
<p class="mt-2 text-dark hide">[[${bundle.L('请分别上传深色与白色 LOGO透明背景建议尺寸 300 × 60')}]]<a class="ml-2 J_logo-gen hide" href="###genlogo"><i class="icon mdi mdi-pencil-ruler"></i> 制作 LOGO</a></p>
</td>
</tr>
<tr>

View file

@ -191,6 +191,11 @@ const _toggleImage = function (el, init) {
_$imgCurrent.find('>i').css('background-image', `url(${rb.baseUrl}/assets/img/s.gif)`)
changeValue({ target: { name: _$imgCurrent.data('id'), value: '' } })
})
$img
.find('.J_logo-gen')
.removeAttr('title')
.off('click')
.on('click', () => {})
}
class DlgMM extends RbAlert {

View file

@ -0,0 +1,23 @@
package com.rebuild.utils;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;
import java.io.File;
/**
*/
class ImageMakerTest {
@SuppressWarnings("deprecation")
@Test
void makeLogo() {
File tmp = new File(FileUtils.getTempDirectory(), "logo.png");
ImageMaker.makeLogo("锐昉科技", null, tmp);
System.out.println(tmp);
}
@Test
void makeAvatar() {
}
}