From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master
---
jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsSignServiceImpl.java | 570 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 570 insertions(+), 0 deletions(-)
diff --git a/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsSignServiceImpl.java b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsSignServiceImpl.java
new file mode 100644
index 0000000..ffadbbb
--- /dev/null
+++ b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/limsService/impl/LimsSignServiceImpl.java
@@ -0,0 +1,570 @@
+package jnpf.limsService.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import jnpf.audit.model.AuditEventDTO;
+import jnpf.audit.sdk.AuditClient;
+import jnpf.audit.sdk.annotation.AuditLog;
+import jnpf.audit.AuditConsts;
+import jnpf.base.UserInfo;
+import jnpf.base.service.SuperServiceImpl;
+import jnpf.exception.DataException;
+import jnpf.limsEntity.LimsModifySignPasswordParam;
+import jnpf.limsEntity.LimsSignEntity;
+import jnpf.limsEntity.LimsSignUsageTarget;
+import jnpf.limsEntity.LimsSignVerifyParam;
+import jnpf.limsEntity.LimsUserSignPasswordEntity;
+import jnpf.limsEntity.SignOpType;
+import jnpf.limsMapper.LimsSignMapper;
+import jnpf.limsMapper.LimsUserSignPasswordMapper;
+import jnpf.limsService.LimsSignService;
+import jnpf.limsService.support.LimsSignUsageSupport;
+import jnpf.permission.UserApi;
+import jnpf.permission.entity.UserEntity;
+import jnpf.util.JsonUtil;
+import jnpf.util.Md5Util;
+import jnpf.util.RandomUtil;
+import jnpf.util.RedisUtil;
+import jnpf.util.UserProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.NoTransactionException;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+@Service
+public class LimsSignServiceImpl
+ extends SuperServiceImpl<LimsSignMapper, LimsSignEntity>
+ implements LimsSignService {
+
+ private static final String DEFAULT_INIT_PASSWORD = "123456";
+
+ @Autowired
+ private LimsUserSignPasswordMapper userSignPasswordMapper;
+
+ @Autowired
+ private UserApi userApi;
+
+ @Autowired
+ private RedisUtil redisUtil;
+
+ /** audit.enabled=false 鏃� AuditClient Bean 涓嶅瓨鍦紝绛惧悕涓氬姟浠嶉』鍙惎鍔ㄣ�� */
+ @Autowired
+ private ObjectProvider<AuditClient> auditClientProvider;
+
+ /**
+ * 鐢靛瓙绛惧悕锛堝綋鍓嶇櫥褰曠敤鎴凤級銆傚眰 2 鍩嬬偣锛屾垚鍔� {@code SIGN} / 澶辫触 {@code SIGN_FAIL}銆�
+ *
+ * <p>涓変釜闈為粯璁ょ殑娉ㄨВ椤癸紝姣忎釜閮芥槸琚疄鎹�煎嚭鏉ョ殑锛屽埆闅忔墜鏀瑰洖榛樿锛�
+ * <ul>
+ * <li>{@code bizCodeRequired=false}鈥斺�旂鍚�**甯稿父鍏堜簬涓氬姟瀵硅薄瀛樺湪**锛堝厛绛惧悕鎷� biz_sign銆�
+ * 鍐嶅垱寤鸿楠屽崟锛屾鏃� dataId 涓虹┖锛夈�傜暀榛樿 true 浼氳绛惧悕浜嬩欢琚潤榛樿烦杩囷紝
+ * 鍙湪鏃ュ織閲岀暀涓�琛� WARN銆�</li>
+ * <li>{@code failureAction="SIGN_FAIL"}鈥斺�斿け璐ュ繀椤荤暀鐥曪細绛惧悕澶辫触鍦ㄥ悎瑙勪笂鏄嫭绔嬪叧娉ㄥ璞�
+ * 锛堣繛缁け璐ュ彲鑳芥槸鏈変汉鍦ㄨ瘯瀵嗙爜锛夈�傛湰鏂规硶鐨勫叏閮ㄥけ璐ヨ矾寰勯兘鏄粠 service 鎶�
+ * {@code DataException}锛�"绛惧悕瀵嗙爜閿欒"/"璐︽埛涓庡綋鍓嶇櫥褰曠敤鎴蜂笉绗�"/鈥︼級锛�
+ * 鍒囬潰 catch 寰楀埌锛屽紓甯� message 鑷姩钀� {@code reason}銆�</li>
+ * <li>{@code password: '***'}鈥斺�斿瘑鐮佸瓧娈�**鍛堢幇浣嗕笉璁板��**锛岀敤鐨勬槸鍏ㄥ眬绾﹀畾
+ * {@link jnpf.audit.diff.AuditFieldDiff#MASKED}锛堜笌灞� 0 蹇収銆佸眰 1
+ * masked-columns 鍚屼竴涓父閲忥級銆傚璁¤〃 append-only 淇濈暀 10 骞达紝鏄庢枃杩涘幓娲椾笉鎺夈��</li>
+ * </ul>
+ *
+ * <p>{@code targetIdExpr="#result"} 鍙湪鎴愬姛璺緞姹傚�煎嚭 signId锛�= {@code lims_sign.f_id}锛夛紱
+ * 澶辫触鏃� result 涓� null锛屽垏闈㈤�愬瓧娈靛厹搴曞悗璇ュ垪鐣欑┖锛屼簨浠剁収璁般��
+ *
+ * <p><b>2026-07-31 璧锋垚鍔熶笉鍐嶅綋鍦鸿褰�</b>锛坽@code recordSuccess=false}锛夛細楠岀鎴愬姛鍚庝笟鍔¤嫢澶辫触锛�
+ * 搴撻噷浼氱暀涓嬩竴鏉℃棤瀵瑰簲涓氬姟鍔ㄤ綔鐨勫鍎跨鍚嶄簨浠垛�斺�旀妧鏈笂鑷唇锛岃嵂鍘傚悎瑙勪笂涓嶆帴鍙�
+ * 锛堢鍚� + 涓氬姟鏄竴涓暣浣擄級銆傛垚鍔熶簨瀹炴敼鐢卞眰 0 鍦ㄨ〃鍗曚繚瀛樻垚鍔熷悗锛屾嵁 {@code biz_sign}
+ * 绛夌鍚嶅瓧娈靛洖璇绘湰琛ㄨˉ鍐欙紝涓庝笟鍔′簨浠跺叡鐢� operationId銆傚け璐ヤ晶 {@code SIGN_FAIL} 涓�瀛楁湭鍔ㄣ��
+ */
+ @Override
+ @AuditLog(
+ eventType = AuditConsts.TYPE_E_SIGNATURE,
+ action = "SIGN",
+ label = "鐢靛瓙绛惧悕",
+ failureAction = "SIGN_FAIL",
+ failureLabel = "鐢靛瓙绛惧悕 澶辫触",
+ recordSuccess = false, // 鎴愬姛浜嬪疄鐢卞眰 0 鍦ㄤ笟鍔′繚瀛樻垚鍔熷悗琛ュ啓锛岃 spec 搂6
+ bizType = "SIGN",
+ bizCodeExpr = "#param?.dataId",
+ bizCodeRequired = false,
+ targetTable = "lims_sign",
+ targetIdExpr = "#result",
+ reasonExpr = "#param?.note",
+ extraExpr = "{'accountName': #param?.accountName, 'opType': #param?.opType,"
+ + " 'password': T(jnpf.audit.diff.AuditFieldDiff).MASKED}"
+ )
+ public String verifyAndRecord(LimsSignVerifyParam param) {
+ LimsSignEntity sign = verifyCurrentUserAndRecord(param);
+ return sign.getId();
+ }
+
+ private LimsSignEntity verifyCurrentUserAndRecord(LimsSignVerifyParam param) {
+ if (param == null) {
+ throw new DataException("鍏ュ弬涓嶈兘涓虹┖");
+ }
+ validateVerifyParam(param);
+
+ UserInfo userInfo = UserProvider.getUser();
+ if (userInfo == null || userInfo.getUserId() == null) {
+ throw new DataException("褰撳墠鏈櫥褰�");
+ }
+ if (!param.getAccountName().equals(userInfo.getUserAccount())) {
+ throw new DataException("璐︽埛涓庡綋鍓嶇櫥褰曠敤鎴蜂笉绗�");
+ }
+
+ verifySignPassword(userInfo.getUserId(), param.getPassword(), "鎮ㄨ繕鏈缃鍚嶅瘑鐮侊紝璇峰厛鍒般�屼釜浜轰腑蹇� 鈫� 淇敼绛惧悕瀵嗙爜銆嶅垵濮嬪寲");
+ return saveSign(param, userInfo.getUserId());
+ }
+
+ /**
+ * 鐢靛瓙绛惧悕锛堟寚瀹氱敤鎴凤紝闈炲綋鍓嶇櫥褰曚汉锛夈�傚煁鐐瑰彛寰勫悓 {@link #verifyAndRecord}锛屼袱澶勫樊寮傦細
+ * <ul>
+ * <li>{@code targetIdExpr="#result?.id"} 鐢�**瀹夊叏瀵艰埅**鈥斺�旀湰鏂规硶杩斿洖瀹炰綋鑰岄潪 String锛�
+ * 澶辫触鏃� result 涓� null锛岄潪瀹夊叏瀵艰埅浼氭姏 SpelEvaluationException锛堝垏闈㈣櫧鏈夐�愬瓧娈靛厹搴曪紝
+ * 浣嗚鎰忓浘鏄惧紡姣斾緷璧栧厹搴曞ソ锛夈��</li>
+ * <li>extra 澶氳 {@code allowSelf}鈥斺�斿畠鍐冲畾"鏄惁鍏佽缁欒嚜宸辩鍚�"锛屾槸杩欎釜绔偣鐙湁鐨勬巿鏉冨紑鍏筹紝
+ * 浜嬪悗杩借矗鏃堕渶瑕佺煡閬撳綋鏃朵紶鐨勬槸浠�涔堛��</li>
+ * </ul>
+ *
+ * <p><b>2026-07-31 璧锋垚鍔熶笉鍐嶅綋鍦鸿褰�</b>锛坽@code recordSuccess=false}锛夛細楠岀鎴愬姛鍚庝笟鍔¤嫢澶辫触锛�
+ * 搴撻噷浼氱暀涓嬩竴鏉℃棤瀵瑰簲涓氬姟鍔ㄤ綔鐨勫鍎跨鍚嶄簨浠垛�斺�旀妧鏈笂鑷唇锛岃嵂鍘傚悎瑙勪笂涓嶆帴鍙�
+ * 锛堢鍚� + 涓氬姟鏄竴涓暣浣擄級銆傛垚鍔熶簨瀹炴敼鐢卞眰 0 鍦ㄨ〃鍗曚繚瀛樻垚鍔熷悗锛屾嵁 {@code biz_sign}
+ * 绛夌鍚嶅瓧娈靛洖璇绘湰琛ㄨˉ鍐欙紝涓庝笟鍔′簨浠跺叡鐢� operationId銆傚け璐ヤ晶 {@code SIGN_FAIL} 涓�瀛楁湭鍔ㄣ��
+ */
+ @Override
+ @AuditLog(
+ eventType = AuditConsts.TYPE_E_SIGNATURE,
+ action = "SIGN",
+ label = "鐢靛瓙绛惧悕锛堟寚瀹氱敤鎴凤級",
+ failureAction = "SIGN_FAIL",
+ failureLabel = "鐢靛瓙绛惧悕锛堟寚瀹氱敤鎴凤級澶辫触",
+ recordSuccess = false, // 鎴愬姛浜嬪疄鐢卞眰 0 鍦ㄤ笟鍔′繚瀛樻垚鍔熷悗琛ュ啓锛岃 spec 搂6
+ bizType = "SIGN",
+ bizCodeExpr = "#param?.dataId",
+ bizCodeRequired = false,
+ targetTable = "lims_sign",
+ targetIdExpr = "#result?.id",
+ reasonExpr = "#param?.note",
+ extraExpr = "{'accountName': #param?.accountName, 'opType': #param?.opType,"
+ + " 'allowSelf': #param?.allowSelf,"
+ + " 'password': T(jnpf.audit.diff.AuditFieldDiff).MASKED}"
+ )
+ public LimsSignEntity verifyUserAndRecord(LimsSignVerifyParam param) {
+ if (param == null) {
+ throw new DataException("鍏ュ弬涓嶈兘涓虹┖");
+ }
+ validateVerifyParam(param);
+
+ UserInfo userInfo = UserProvider.getUser();
+ if (userInfo == null || userInfo.getUserId() == null) {
+ throw new DataException("褰撳墠鏈櫥褰�");
+ }
+ boolean allowSelf = isYes(param.getAllowSelf());
+ if (!allowSelf && param.getAccountName().equals(userInfo.getUserAccount())) {
+ throw new DataException("涓嶅厑璁搁獙璇佸綋鍓嶇櫥褰曠敤鎴�");
+ }
+
+ UserEntity signUser = userApi.getInfoByAccount(param.getAccountName());
+ if (signUser == null || signUser.getId() == null || signUser.getId().isEmpty()) {
+ throw new DataException("绛惧悕璐︽埛涓嶅瓨鍦�");
+ }
+ if (!allowSelf && Objects.equals(userInfo.getUserId(), signUser.getId())) {
+ throw new DataException("涓嶅厑璁搁獙璇佸綋鍓嶇櫥褰曠敤鎴�");
+ }
+
+ verifySignPassword(signUser.getId(), param.getPassword(), "璇ョ敤鎴疯繕鏈缃鍚嶅瘑鐮�");
+ return saveSign(param, signUser.getId());
+ }
+
+ private void validateVerifyParam(LimsSignVerifyParam param) {
+ String opTypeCode = param.getOpType();
+
+ // op_type 鑷柟妗� C 璧锋敼涓哄彲閫夛細
+ // - 浼犱簡锛氬繀椤绘槸鍚堟硶 SignOpType锛坋ager 妯″紡锛屼笟鍔℃帴鍙d粛鎸夋灇涓炬牎楠岋級
+ // - 娌′紶锛歭azy 妯″紡锛屼笟鍔℃帴鍙f敹鍒� biz_sign 鏃跺啀鍥炲啓 op_type
+ if (opTypeCode != null && !opTypeCode.isEmpty() && !SignOpType.isValidCode(opTypeCode)) {
+ throw new DataException("闈炴硶鐨勭鍚嶆搷浣滅被鍨�: " + opTypeCode);
+ }
+ if (param.getAccountName() == null || param.getAccountName().isEmpty()) {
+ throw new DataException("璐︽埛鍚嶇О涓嶈兘涓虹┖");
+ }
+ if (param.getPassword() == null || param.getPassword().isEmpty()) {
+ throw new DataException("绛惧悕瀵嗙爜涓嶈兘涓虹┖");
+ }
+ if (param.getNote() == null || param.getNote().isEmpty()) {
+ throw new DataException("璇存槑 / 鍘熷洜涓嶈兘涓虹┖");
+ }
+ }
+
+ private void verifySignPassword(String userId, String password, String unsetMessage) {
+ QueryWrapper<LimsUserSignPasswordEntity> pq = new QueryWrapper<>();
+ pq.eq("user_id", userId);
+ pq.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ LimsUserSignPasswordEntity pwdRow = userSignPasswordMapper.selectOne(pq);
+ if (pwdRow == null || pwdRow.getPasswordHash() == null || pwdRow.getPasswordHash().isEmpty()) {
+ throw new DataException(unsetMessage);
+ }
+
+ String inputHash = Md5Util.getStringMd5(password + pwdRow.getSecretkey().toLowerCase());
+ if (!inputHash.equals(pwdRow.getPasswordHash())) {
+ throw new DataException("绛惧悕瀵嗙爜閿欒");
+ }
+ }
+
+ private boolean isYes(String value) {
+ return "yes".equalsIgnoreCase(value == null ? "" : value.trim());
+ }
+
+ private LimsSignEntity saveSign(LimsSignVerifyParam param, String creatorUserId) {
+ LimsSignEntity sign = new LimsSignEntity();
+ sign.setId(RandomUtil.uuId());
+ sign.setDataId(param.getDataId());
+ sign.setOpType(param.getOpType());
+ sign.setAccountName(param.getAccountName());
+ sign.setNote(param.getNote());
+ sign.setMetaData(param.getMetaData());
+ sign.setCreatorUserId(creatorUserId);
+ // tenantId / creatorTime 鐢� MetaObjectHandler 濉�
+ this.save(sign);
+ return sign;
+ }
+
+ @Override
+ public LimsSignEntity requireValidSign(String signId, SignOpType expectedOpType, String expectedDataId) {
+ if (signId == null || signId.isEmpty()) {
+ throw new DataException("缂哄皯鐢靛瓙绛惧悕");
+ }
+ if (expectedOpType == null) {
+ throw new DataException("涓氬姟鏈寚瀹氱鍚嶆搷浣滅被鍨�");
+ }
+ UserInfo userInfo = UserProvider.getUser();
+ if (userInfo == null || userInfo.getUserId() == null) {
+ throw new DataException("褰撳墠鏈櫥褰�");
+ }
+
+ QueryWrapper<LimsSignEntity> q = new QueryWrapper<>();
+ q.eq("f_id", signId);
+ q.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ // 涓嶉檺瀹氬垪锛氳 commit 54c8a52 鈥� selectList 闄愬畾鍒� + 澶氱鎴锋嫤鎴櫒浼氳 entity 涓� null
+ LimsSignEntity sign = this.getBaseMapper().selectOne(q);
+ if (sign == null) {
+ throw new DataException("鐢靛瓙绛惧悕涓嶅瓨鍦ㄦ垨宸插け鏁�");
+ }
+ // 绛惧悕浜哄缁堟牎楠�
+ if (!userInfo.getUserId().equals(sign.getCreatorUserId())) {
+ throw new DataException("鐢靛瓙绛惧悕闈炲綋鍓嶇敤鎴锋墍绛撅紝绂佹浣跨敤");
+ }
+ if (sign.getExtraJson() != null && !sign.getExtraJson().isEmpty()) {
+ throw new DataException("鐢靛瓙绛惧悕宸茶浣跨敤锛岃閲嶆柊绛惧悕");
+ }
+
+ // op_type / data_id 鍚勮嚜鐙珛鏀寔 lazy / eager锛�
+ // 瀛楁涓虹┖ 鈫� 浠呭湪鍐呭瓨涓ˉ鍏紝涓氬姟鎴愬姛鍚庣敱 recordSignUsage 鍘熷瓙璁ら
+ // 瀛楁宸叉湁鍊� 鈫� 涓ユ牸鏍¢獙鍖归厤
+ String existingOpType = sign.getOpType();
+ if (existingOpType == null || existingOpType.isEmpty()) {
+ sign.setOpType(expectedOpType.getCode());
+ } else if (!expectedOpType.getCode().equals(existingOpType)) {
+ throw new DataException("鐢靛瓙绛惧悕鎿嶄綔绫诲瀷涓嶇锛堥渶 " + expectedOpType.getLabel() + "锛�");
+ }
+
+ // expectedDataId 涓� null = 涓氬姟鎺ュ彛涓嶉渶瑕侀敋 data_id锛堝 jiance-task 鎵归噺绫伙級锛�
+ // 杩欑鎯呭喌瀹屽叏涓嶆牎楠屼篃涓嶅洖鍐� sign.data_id锛堜繚鐣� verify 鏃剁殑鍘熷�硷級銆�
+ if (expectedDataId != null) {
+ String existingDataId = sign.getDataId();
+ if (existingDataId == null || existingDataId.isEmpty()) {
+ sign.setDataId(expectedDataId);
+ } else if (!expectedDataId.equals(existingDataId)) {
+ throw new DataException("鐢靛瓙绛惧悕涓氬姟瀵硅薄涓嶇");
+ }
+ }
+ return sign;
+ }
+
+ /**
+ * 銆屽師瀛愯棰� + 鍙� SIGN_USED銆嶇殑缁勫悎鍏ュ彛锛岃涔変笌鎷嗗垎鍓嶅畬鍏ㄤ竴鑷淬��
+ *
+ * <p>2026-07-31 鎷嗘垚涓ゆ锛坽@link #claimSignUsageOnly} + {@link #publishSignUsedEvents}锛夛細
+ * 鍦ㄧ嚎琛ㄥ崟璺緞鍙渶瑕佸墠鍗婃鈥斺�旇〃鍗曚繚瀛樻湰韬凡浜х敓灞� 0 浜嬩欢锛屽眰 0 浼氭嵁琛ㄥ崟閲岀殑绛惧悕瀛楁琛ュ啓
+ * 涓�鏉$鍚嶄簨浠讹紝杩欓噷鍐嶅彂 {@code SIGN_USED} 灏辨槸鍚屼竴娆$鍚嶄袱鏉¤瘉鎹紙D8锛夈��
+ * <b>椤哄簭涓嶅彲璋冩崲</b>锛氳棰嗗け璐ヤ細鏍囪浜嬪姟鍥炴粴锛屽繀椤诲厛璁ら鎴愬姛鎵嶅厑璁稿彂浜嬩欢銆�
+ */
+ @Override
+ public void recordSignUsage(LimsSignEntity sign,
+ SignOpType opType,
+ Collection<String> requestedTargetIds,
+ Collection<LimsSignUsageTarget> actualTargets) {
+ claimSignUsageOnly(sign, opType, requestedTargetIds, actualTargets);
+ publishSignUsedEvents(sign, opType, actualTargets);
+ }
+
+ @Override
+ public void claimSignUsageOnly(LimsSignEntity sign,
+ SignOpType opType,
+ Collection<String> requestedTargetIds,
+ Collection<LimsSignUsageTarget> actualTargets) {
+ List<LimsSignUsageTarget> normalized = LimsSignUsageSupport.normalizeTargets(actualTargets);
+ if (!signUsageApplicable(sign, opType, normalized)) {
+ return;
+ }
+ String usageJson = JsonUtil.getObjectToString(
+ LimsSignUsageSupport.signExtra(requestedTargetIds, normalized));
+ claimSignUsage(sign, opType, usageJson);
+ }
+
+ private void publishSignUsedEvents(LimsSignEntity sign,
+ SignOpType opType,
+ Collection<LimsSignUsageTarget> actualTargets) {
+ List<LimsSignUsageTarget> normalized = LimsSignUsageSupport.normalizeTargets(actualTargets);
+ if (!signUsageApplicable(sign, opType, normalized)) {
+ return;
+ }
+ for (LimsSignUsageSupport.UsageGroup group : LimsSignUsageSupport.groupByBizCode(normalized)) {
+ AuditEventDTO event = AuditEventDTO.builder()
+ .clientEventId(LimsSignUsageSupport.clientEventId(sign.getId(), opType, group))
+ .eventType(AuditConsts.TYPE_E_SIGNATURE)
+ .actionCode(LimsSignUsageSupport.ACTION_SIGN_USED)
+ .actionLabel(opType.getLabel() + " 路 鐢靛瓙绛惧悕")
+ .sourceLayer(3)
+ .targetTable("lims_sign")
+ .targetId(sign.getId())
+ .bizType(group.getBizType())
+ .bizModule(group.getBizModule())
+ .bizCode(group.getBizCode())
+ .reason(sign.getNote())
+ .extra(JsonUtil.getObjectToString(
+ LimsSignUsageSupport.eventExtra(sign, opType, group)))
+ .build();
+ auditClientProvider.ifAvailable(client -> client.record(event));
+ }
+ }
+
+ /**
+ * 璁ら涓庡彂浜嬩欢鍏辩敤鐨勫墠缃垽瀹氾細浠讳竴涓嶆弧瓒冲垯鏈銆岀鍚嶈浣跨敤銆嶈涓烘病鏈夊彂鐢燂紝
+ * 鏃笉璁ら涔熶笉鍙戜簨浠躲�備袱渚у叡鐢ㄤ竴浠斤紝閬垮厤"璁ら浜嗕絾涓嶅彂浜嬩欢"杩欑被涓嶅绉般��
+ */
+ private boolean signUsageApplicable(LimsSignEntity sign,
+ SignOpType opType,
+ List<LimsSignUsageTarget> normalized) {
+ return sign != null && sign.getId() != null && !sign.getId().isEmpty()
+ && opType != null && !normalized.isEmpty();
+ }
+
+ private void claimSignUsage(LimsSignEntity sign, SignOpType opType, String usageJson) {
+ UpdateWrapper<LimsSignEntity> update = new UpdateWrapper<>();
+ update.eq("f_id", sign.getId());
+ update.eq("f_creator_user_id", sign.getCreatorUserId());
+ update.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ update.and(w -> w.isNull("op_type").or().eq("op_type", "")
+ .or().eq("op_type", opType.getCode()));
+ update.and(w -> w.isNull("extra_json").or().eq("extra_json", ""));
+ update.set("op_type", opType.getCode());
+ update.set("extra_json", usageJson);
+
+ String dataId = sign.getDataId();
+ if (dataId == null || dataId.isEmpty()) {
+ update.and(w -> w.isNull("data_id").or().eq("data_id", ""));
+ } else {
+ update.and(w -> w.isNull("data_id").or().eq("data_id", "")
+ .or().eq("data_id", dataId));
+ update.set("data_id", dataId);
+ }
+
+ if (this.getBaseMapper().update(null, update) != 1) {
+ throw signUsageFailure("鐢靛瓙绛惧悕宸茶鍏朵粬涓氬姟浣跨敤鎴栫姸鎬佸凡鍙樻洿");
+ }
+ sign.setOpType(opType.getCode());
+ sign.setExtraJson(usageJson);
+ }
+
+ private DataException signUsageFailure(String message) {
+ if (TransactionSynchronizationManager.isActualTransactionActive()) {
+ try {
+ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+ } catch (NoTransactionException ignored) {
+ // 璋冪敤鏂逛笉鏄敱 @Transactional 浠g悊杩涘叆鏃讹紝浠嶅悜涓婃姏閿欓樆姝㈢户缁褰曚簨浠躲��
+ }
+ }
+ return new DataException(message);
+ }
+
+ @Override
+ public int[] initAllUsers() {
+ UserInfo userInfo = UserProvider.getUser();
+ if (userInfo == null) {
+ throw new DataException("褰撳墠鏈櫥褰�");
+ }
+
+ // 1. 鍙栨墍鏈夋湭杞垹鐨� base_user
+ List<Map<String, Object>> users = userSignPasswordMapper.selectAllActiveBaseUsers();
+ if (users == null || users.isEmpty()) {
+ return new int[]{0, 0};
+ }
+
+ // 2. 鍙栧凡鍒濆鍖栫殑 user_id 闆嗗悎锛堜竴娆℃煡閬垮厤寰幆閲� N+1锛�
+ Set<String> existed = new HashSet<>();
+ QueryWrapper<LimsUserSignPasswordEntity> exq = new QueryWrapper<>();
+ exq.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ List<LimsUserSignPasswordEntity> exList = userSignPasswordMapper.selectList(exq);
+ if (exList != null) {
+ for (LimsUserSignPasswordEntity r : exList) {
+ if (r != null && r.getUserId() != null) {
+ existed.add(r.getUserId());
+ }
+ }
+ }
+
+ // 3. 杩囨护鍚庢壒閲忔彃鍏�
+ int inserted = 0;
+ int skipped = 0;
+ Date now = new Date();
+ for (Map<String, Object> u : users) {
+ String uid = u.get("id") == null ? null : u.get("id").toString();
+ String tenantId = u.get("tenantId") == null ? "0" : u.get("tenantId").toString();
+ if (uid == null || uid.isEmpty()) {
+ continue;
+ }
+ if (existed.contains(uid)) {
+ skipped++;
+ continue;
+ }
+ // RandomUtil 浠呮彁渚� uuId / enUuId锛屾棤 randomString銆傛埅 8 浣嶄綔涓虹嫭绔嬬洂锛岃冻澶熷尯鍒嗐��
+ String secretkey = RandomUtil.uuId().substring(0, 8);
+ String hash = Md5Util.getStringMd5(DEFAULT_INIT_PASSWORD + secretkey.toLowerCase());
+
+ LimsUserSignPasswordEntity row = new LimsUserSignPasswordEntity();
+ row.setId(RandomUtil.uuId());
+ row.setTenantId(tenantId);
+ row.setUserId(uid);
+ row.setPasswordHash(hash);
+ row.setSecretkey(secretkey);
+ row.setCreatorTime(now);
+ row.setCreatorUserId(userInfo.getUserId());
+ userSignPasswordMapper.insert(row);
+ inserted++;
+ }
+ return new int[]{inserted, skipped};
+ }
+
+ /**
+ * 淇敼绛惧悕瀵嗙爜銆�**鍔ㄤ綔鐮佷笌绛惧悕鏈韩鍒嗗紑**锛坽@code SIGN_PWD_CHANGE}锛夆�斺�斿畠涓嶆槸"绛句簡涓�娆″悕"锛�
+ * 鑰屾槸"鏀瑰彉浜嗘鍚庢墍鏈夌鍚嶇殑鍑嵁"锛屽悎瑙勪笂鏄嫭绔嬬殑鍏抽敭鍔ㄤ綔锛屾贩杩� SIGN 浼氭薄鏌撶鍚嶈鏁般��
+ *
+ * <p>鍏ュ弬涓変釜瀛楁鍏ㄦ槸瀵嗙爜锛坅ccountPassword / password / repeatPassword锛夛紝
+ * 鍥犳 extra 閲�**涓変釜涓�寰� MASKED**锛氳"杩欐鎻愪氦浜嗗摢浜涘嚟鎹瓧娈�"鑰屼笉璁板�笺��
+ * 鏈柟娉曟病鏈� note 瀛楁锛屾晠 {@code reasonExpr} 涓嶉厤锛宺eason 鐣欑┖鈥斺��
+ * 澶辫触鐨勬妧鏈師鍥狅紙"楠岃瘉鐮侀敊璇垨宸茶繃鏈�"/"璐︽埛瀵嗙爜閿欒"/鈥︼級鐢卞垏闈㈣惤 extra.failureReason銆�
+ *
+ * <p>{@code bizCodeRequired=false}锛氭敼瀵嗙爜澶╃劧娌℃湁涓氬姟鍗曞彿銆�
+ */
+ @Override
+ @AuditLog(
+ eventType = AuditConsts.TYPE_E_SIGNATURE,
+ action = "SIGN_PWD_CHANGE",
+ label = "淇敼绛惧悕瀵嗙爜",
+ failureAction = "SIGN_PWD_CHANGE_FAIL",
+ failureLabel = "淇敼绛惧悕瀵嗙爜 澶辫触",
+ bizType = "SIGN",
+ bizCodeExpr = "",
+ bizCodeRequired = false,
+ targetTable = "lims_user_sign_password",
+ extraExpr = "{'accountPassword': T(jnpf.audit.diff.AuditFieldDiff).MASKED,"
+ + " 'password': T(jnpf.audit.diff.AuditFieldDiff).MASKED,"
+ + " 'repeatPassword': T(jnpf.audit.diff.AuditFieldDiff).MASKED}"
+ )
+ public void modifySignPassword(LimsModifySignPasswordParam param) {
+ if (param == null) {
+ throw new DataException("鍏ュ弬涓嶈兘涓虹┖");
+ }
+
+ // 1. 楠岃瘉鐮�
+ if (param.getTimestamp() == null || param.getTimestamp().isEmpty()
+ || param.getCode() == null || param.getCode().isEmpty()) {
+ throw new DataException("璇疯緭鍏ラ獙璇佺爜");
+ }
+ Object cached = redisUtil.getString(param.getTimestamp());
+ String captcha = cached == null ? "" : String.valueOf(cached);
+ if (captcha.isEmpty() || !param.getCode().equalsIgnoreCase(captcha)) {
+ throw new DataException("楠岃瘉鐮侀敊璇垨宸茶繃鏈�");
+ }
+
+ // 2. 褰撳墠鐢ㄦ埛
+ UserInfo userInfo = UserProvider.getUser();
+ if (userInfo == null || userInfo.getUserId() == null) {
+ throw new DataException("褰撳墠鏈櫥褰�");
+ }
+
+ // 3. 璐︽埛瀵嗙爜锛堜笌 /Users/Current/Actions/ModifyPassword 鏍¢獙閫昏緫涓�鑷达細
+ // 鍓嶇 MD5(鏄庢枃)锛屽悗绔� Md5(md5_password + secretkey) == base_user.f_password锛�
+ if (param.getAccountPassword() == null || param.getAccountPassword().isEmpty()) {
+ throw new DataException("璇疯緭鍏ヨ处鎴峰瘑鐮�");
+ }
+ UserEntity user = userApi.getInfoById(userInfo.getUserId());
+ if (user == null) {
+ throw new DataException("鐢ㄦ埛涓嶅瓨鍦�");
+ }
+ String expectAccountHash = Md5Util.getStringMd5(
+ param.getAccountPassword().toLowerCase() + user.getSecretkey().toLowerCase());
+ if (!expectAccountHash.equals(user.getPassword())) {
+ throw new DataException("璐︽埛瀵嗙爜閿欒");
+ }
+
+ // 4. 鏂扮鍚嶅瘑鐮�
+ if (param.getPassword() == null || param.getPassword().isEmpty()) {
+ throw new DataException("鏂扮鍚嶅瘑鐮佷笉鑳戒负绌�");
+ }
+ if (!param.getPassword().equals(param.getRepeatPassword())) {
+ throw new DataException("涓ゆ杈撳叆鐨勬柊绛惧悕瀵嗙爜涓嶄竴鑷�");
+ }
+
+ // 5. 鍐欏叆 / 鏇存柊 lims_user_sign_password
+ QueryWrapper<LimsUserSignPasswordEntity> pq = new QueryWrapper<>();
+ pq.eq("user_id", userInfo.getUserId());
+ pq.and(w -> w.isNull("f_delete_mark").or().ne("f_delete_mark", 1));
+ LimsUserSignPasswordEntity existing = userSignPasswordMapper.selectOne(pq);
+
+ String secretkey = RandomUtil.uuId().substring(0, 8);
+ String hash = Md5Util.getStringMd5(param.getPassword() + secretkey.toLowerCase());
+ Date now = new Date();
+ if (existing == null) {
+ LimsUserSignPasswordEntity row = new LimsUserSignPasswordEntity();
+ row.setId(RandomUtil.uuId());
+ row.setUserId(userInfo.getUserId());
+ row.setPasswordHash(hash);
+ row.setSecretkey(secretkey);
+ row.setLastResetTime(now);
+ // tenantId / creatorUserId / creatorTime 鐢� MetaObjectHandler 濉�
+ userSignPasswordMapper.insert(row);
+ } else {
+ existing.setPasswordHash(hash);
+ existing.setSecretkey(secretkey);
+ existing.setLastResetTime(now);
+ // tenantId / lastModifyUserId / lastModifyTime 鐢� MetaObjectHandler 濉�
+ userSignPasswordMapper.updateById(existing);
+ }
+
+ // 娑堣�楀凡鐢ㄩ獙璇佺爜锛岄槻姝㈤噸鏀�
+ redisUtil.remove(param.getTimestamp());
+ }
+
+ @Override
+ public void attachExtra(String signId, Object extra) {
+ if (signId == null || signId.isEmpty() || extra == null) {
+ return;
+ }
+ UpdateWrapper<LimsSignEntity> uw = new UpdateWrapper<>();
+ uw.eq("f_id", signId);
+ uw.set("extra_json", JsonUtil.getObjectToString(extra));
+ this.update(null, uw);
+ }
+}
--
Gitblit v1.8.0