This commit is contained in:
wujinliang
2026-08-26 15:32:10 +08:00
parent 897ddd476f
commit f2877df697
7 changed files with 39 additions and 19 deletions
@@ -114,6 +114,7 @@ public class AuthService {
.subject(account.getId().toString()) .subject(account.getId().toString())
.claim("user_id", account.getId()) .claim("user_id", account.getId())
.claim("username", account.getUsername()) .claim("username", account.getUsername())
.claim("display_name", account.getDisplayName() == null ? "" : account.getDisplayName())
.claim("tenant_id", account.getDefaultTenantId()) .claim("tenant_id", account.getDefaultTenantId())
.claim("operator_tenant_id", account.getOperatorTenantId()) .claim("operator_tenant_id", account.getOperatorTenantId())
.claim("membership_id", account.getDefaultMembershipId()) .claim("membership_id", account.getDefaultMembershipId())
@@ -176,7 +177,7 @@ public class AuthService {
JwtClaimsSet claims = JwtClaimsSet.builder().issuer(properties.issuer()) JwtClaimsSet claims = JwtClaimsSet.builder().issuer(properties.issuer())
.audience(List.of(properties.audience())).id(jwtId) .audience(List.of(properties.audience())).id(jwtId)
.issuedAt(issuedAt).expiresAt(expiresAt).subject(account.getId().toString()) .issuedAt(issuedAt).expiresAt(expiresAt).subject(account.getId().toString())
.claim("user_id", account.getId()).claim("username", account.getUsername()) .claim("user_id", account.getId()).claim("username", account.getUsername()).claim("display_name", account.getDisplayName() == null ? "" : account.getDisplayName())
.claim("tenant_id", account.getDefaultTenantId()).claim("operator_tenant_id", account.getOperatorTenantId()) .claim("tenant_id", account.getDefaultTenantId()).claim("operator_tenant_id", account.getOperatorTenantId())
.claim("membership_id", account.getDefaultMembershipId()).claim("app_code", "MERCHANT") .claim("membership_id", account.getDefaultMembershipId()).claim("app_code", "MERCHANT")
.claim("client_type", authorization.getClientType()).claim("token_version", account.getTokenVersion()) .claim("client_type", authorization.getClientType()).claim("token_version", account.getTokenVersion())
@@ -7,12 +7,12 @@ import java.util.Set;
* 统一认证用户主体,携带用户、租户、成员身份、应用端、客户端类型及权限信息。 * 统一认证用户主体,携带用户、租户、成员身份、应用端、客户端类型及权限信息。
*/ */
public record AuthenticatedUser(Long userId, Long tenantId, Long operatorTenantId, Long membershipId, public record AuthenticatedUser(Long userId, Long tenantId, Long operatorTenantId, Long membershipId,
String username, String appCode, String clientType, String username, String displayName, String appCode, String clientType,
Set<String> permissions, boolean impersonated, Long actorUserId, Set<String> permissions, boolean impersonated, Long actorUserId,
Long actorMembershipId, boolean passwordChangeRequired) implements Principal { Long actorMembershipId, boolean passwordChangeRequired) implements Principal {
public AuthenticatedUser(Long userId, Long tenantId, Long operatorTenantId, Long membershipId, public AuthenticatedUser(Long userId, Long tenantId, Long operatorTenantId, Long membershipId,
String username, String appCode, String clientType, Set<String> permissions) { String username, String appCode, String clientType, Set<String> permissions) {
this(userId, tenantId, operatorTenantId, membershipId, username, appCode, clientType, this(userId, tenantId, operatorTenantId, membershipId, username, null, appCode, clientType,
permissions, false, null, null, false); permissions, false, null, null, false);
} }
public AuthenticatedUser { public AuthenticatedUser {
@@ -37,6 +37,7 @@ public class JwtClaimsMapper {
longClaim(jwt, "operator_tenant_id"), longClaim(jwt, "operator_tenant_id"),
longClaim(jwt, "membership_id"), longClaim(jwt, "membership_id"),
username, username,
firstNonBlankNullable(jwt.getClaimAsString("displayName"), jwt.getClaimAsString("display_name")),
appCode, appCode,
jwt.getClaimAsString("client_type"), jwt.getClaimAsString("client_type"),
permissions, permissions,
@@ -86,4 +87,9 @@ public class JwtClaimsMapper {
for (String value : values) if (value != null && !value.isBlank()) return value; for (String value : values) if (value != null && !value.isBlank()) return value;
return "unknown"; return "unknown";
} }
private String firstNonBlankNullable(String... values) {
for (String value : values) if (value != null && !value.isBlank()) return value.trim();
return null;
}
} }
@@ -13,8 +13,8 @@ final class FinanceOperatorSnapshot {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof AuthenticatedUser user if (authentication != null && authentication.getPrincipal() instanceof AuthenticatedUser user
&& user.userId() != null && user.userId() == operatorId && user.userId() != null && user.userId() == operatorId
&& StringUtils.hasText(user.username())) { && (StringUtils.hasText(user.displayName()) || StringUtils.hasText(user.username()))) {
return user.username().trim(); return StringUtils.hasText(user.displayName()) ? user.displayName().trim() : user.username().trim();
} }
return null; return null;
} }
@@ -41,23 +41,26 @@ public class ReceivableStatisticsService {
LocalDate from = month.atDay(1); LocalDate from = month.atDay(1);
LocalDate to = month.plusMonths(1).atDay(1); LocalDate to = month.plusMonths(1).atDay(1);
StringBuilder sql = new StringBuilder(""" StringBuilder sql = new StringBuilder("""
SELECT tenant_id, customer_name_snapshot, SELECT s.tenant_id,
SUM(total_amount) total_receivable, COALESCE(p.customer_name_snapshot, MAX(s.customer_name_snapshot)) customer_name_snapshot,
SUM(paid_amount) total_received, SUM(s.total_amount) total_receivable,
SUM(outstanding_amount) total_outstanding SUM(s.paid_amount) total_received,
FROM fin_settlement SUM(s.outstanding_amount) total_outstanding
WHERE direction='RECEIVABLE' FROM fin_settlement s
AND status<>'VOIDED' LEFT JOIN fin_customer_settlement_profile p
AND currency_code='CNY' ON p.tenant_id=s.tenant_id AND p.status='ACTIVE'
AND generated_date>=? WHERE s.direction='RECEIVABLE'
AND generated_date<? AND s.status<>'VOIDED'
AND s.currency_code='CNY'
AND s.generated_date>=?
AND s.generated_date<?
"""); """);
List<Object> args = new ArrayList<>(List.of(from, to)); List<Object> args = new ArrayList<>(List.of(from, to));
if (customerId != null) { if (customerId != null) {
sql.append(" AND tenant_id=?"); sql.append(" AND s.tenant_id=?");
args.add(customerId); args.add(customerId);
} }
sql.append(" GROUP BY tenant_id,customer_name_snapshot ORDER BY customer_name_snapshot,tenant_id"); sql.append(" GROUP BY s.tenant_id,p.customer_name_snapshot ORDER BY customer_name_snapshot,s.tenant_id");
return jdbc.query(sql.toString(), (rs, rowNum) -> checked(new ReceivableStatisticsRow( return jdbc.query(sql.toString(), (rs, rowNum) -> checked(new ReceivableStatisticsRow(
rs.getLong("tenant_id"), rs.getString("customer_name_snapshot"), CURRENCY, rs.getLong("tenant_id"), rs.getString("customer_name_snapshot"), CURRENCY,
rs.getBigDecimal("total_receivable"), rs.getBigDecimal("total_received"), rs.getBigDecimal("total_receivable"), rs.getBigDecimal("total_received"),
@@ -40,8 +40,8 @@ class ReceivableStatisticsServiceTest {
assertThat(result.records()).containsExactly(rows.get(0)); assertThat(result.records()).containsExactly(rows.get(0));
var sql = org.mockito.ArgumentCaptor.forClass(String.class); var sql = org.mockito.ArgumentCaptor.forClass(String.class);
verify(jdbc).query(sql.capture(), any(RowMapper.class), any(Object[].class)); verify(jdbc).query(sql.capture(), any(RowMapper.class), any(Object[].class));
assertThat(sql.getValue()).contains("direction='RECEIVABLE'", "status<>'VOIDED'", "currency_code='CNY'", assertThat(sql.getValue()).contains("s.direction='RECEIVABLE'", "s.status<>'VOIDED'", "s.currency_code='CNY'",
"generated_date>=?", "generated_date<?", "GROUP BY tenant_id,customer_name_snapshot"); "s.generated_date>=?", "s.generated_date<?", "fin_customer_settlement_profile", "GROUP BY s.tenant_id,p.customer_name_snapshot");
} }
@Test @Test
@@ -28,6 +28,16 @@ class FinanceOperatorSnapshotTest {
assertThat(FinanceOperatorSnapshot.currentName(72L)).isNull(); assertThat(FinanceOperatorSnapshot.currentName(72L)).isNull();
} }
@Test
void prefersAuthenticatedDisplayNameOverLoginUsername() {
AuthenticatedUser user = new AuthenticatedUser(71L, null, null, null,
"12555214478", "廖万昌", "MERCHANT", "WEB", Set.of(), false, null, null, false);
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(user, null));
assertThat(FinanceOperatorSnapshot.currentName(71L)).isEqualTo("廖万昌");
}
@Test @Test
void labelsSystemGeneratedEntriesWithoutInventingAnOperator() { void labelsSystemGeneratedEntriesWithoutInventingAnOperator() {
assertThat(FinanceOperatorSnapshot.currentName(0L)).isEqualTo("系统"); assertThat(FinanceOperatorSnapshot.currentName(0L)).isEqualTo("系统");