新增功能:系统管理-原始记录模板、AB角管理

feature-wangxilei-dev
wxl 2 weeks ago
parent 910fe72a1b
commit 2022c1612e
  1. 2
      lab-auth/src/main/java/org/springblade/auth/granter/SocialTokenGranter.java
  2. 7
      lab-auth/src/main/java/org/springblade/auth/service/BladeUserDetails.java
  3. 3
      lab-auth/src/main/java/org/springblade/auth/service/BladeUserDetailsServiceImpl.java
  4. 1
      lab-auth/src/main/java/org/springblade/auth/support/BladeJwtTokenEnhancer.java
  5. 1
      lab-auth/src/main/java/org/springblade/auth/utils/TokenUtil.java
  6. 7
      lab-service-api/lab-lims-api/src/main/java/org/springblade/lims/entry/ExamineItem.java
  7. 32
      lab-service-api/lab-lims-api/src/main/java/org/springblade/lims/entry/OriginalRecordTemplate.java
  8. 29
      lab-service-api/lab-lims-api/src/main/java/org/springblade/lims/entry/TemplateField.java
  9. 33
      lab-service-api/lab-lims-api/src/main/java/org/springblade/lims/entry/TemplateFieldMapping.java
  10. 13
      lab-service-api/lab-user-api/src/main/java/org/springblade/system/user/entity/User.java
  11. 45
      lab-service/lab-lims/src/main/java/org/springblade/lims/controller/OriginalRecordTemplateController.java
  12. 44
      lab-service/lab-lims/src/main/java/org/springblade/lims/controller/ReagentFormulaController.java
  13. 55
      lab-service/lab-lims/src/main/java/org/springblade/lims/controller/TemplateFieldController.java
  14. 8
      lab-service/lab-lims/src/main/java/org/springblade/lims/mapper/OriginalRecordTemplateMapper.java
  15. 13
      lab-service/lab-lims/src/main/java/org/springblade/lims/mapper/TemplateFieldMapper.java
  16. 13
      lab-service/lab-lims/src/main/java/org/springblade/lims/mapper/TemplateFieldMappingMapper.java
  17. 21
      lab-service/lab-lims/src/main/java/org/springblade/lims/service/IOriginalRecordTemplateService.java
  18. 64
      lab-service/lab-lims/src/main/java/org/springblade/lims/service/ITemplateFieldService.java
  19. 57
      lab-service/lab-lims/src/main/java/org/springblade/lims/service/impl/OriginalRecordTemplateServiceImpl.java
  20. 99
      lab-service/lab-lims/src/main/java/org/springblade/lims/service/impl/TemplateFieldServiceImpl.java
  21. 116
      lab-service/lab-user/src/main/java/org/springblade/labuser/init/AdminAccountInitializer.java
  22. 2
      lab-service/lab-user/src/main/java/org/springblade/system/user/UserApplication.java
  23. 12
      lab-service/lab-user/src/main/java/org/springblade/system/user/controller/UserController.java
  24. 9
      lab-service/lab-user/src/main/java/org/springblade/system/user/service/IUserService.java
  25. 7
      lab-service/lab-user/src/main/java/org/springblade/system/user/service/impl/UserServiceImpl.java
  26. 9
      lab-service/lab-user/src/main/java/org/springblade/system/user/util/RedisKeyExpirationListener.java

@ -97,7 +97,7 @@ public class SocialTokenGranter extends AbstractTokenGranter {
} }
bladeUserDetails = new BladeUserDetails(user.getId(), bladeUserDetails = new BladeUserDetails(user.getId(),
tenantId, result.getData().getOauthId(), user.getName(), user.getRealName(), user.getDeptId(), user.getPostId(), user.getRoleId(), Func.join(result.getData().getRoles()), Func.toStr(userOauth.getAvatar(), TokenUtil.DEFAULT_AVATAR), tenantId, result.getData().getOauthId(), user.getName(), user.getRealName(), user.getDeptId(), user.getPostId(), user.getRoleId(), Func.join(result.getData().getRoles()), Func.toStr(userOauth.getAvatar(), TokenUtil.DEFAULT_AVATAR),
userOauth.getUsername(), AuthConstant.ENCRYPT + user.getPassword(), detail, true, true, true, true, userOauth.getUsername(), AuthConstant.ENCRYPT + user.getPassword(), detail, false, true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList(Func.join(result.getData().getRoles()))); AuthorityUtils.commaSeparatedStringToAuthorityList(Func.join(result.getData().getRoles())));
} else { } else {
throw new InvalidGrantException("social grant failure, feign client return error"); throw new InvalidGrantException("social grant failure, feign client return error");

@ -64,8 +64,12 @@ public class BladeUserDetails extends User {
* 用户详情 * 用户详情
*/ */
private final Kv detail; private final Kv detail;
/**
* 强制修改密码
*/
private final boolean forcePasswordChange;
public BladeUserDetails(Long userId, String tenantId, String oauthId, String name, String realName, String deptId, String postId, String roleId, String roleName, String avatar, String username, String password, Kv detail, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { public BladeUserDetails(Long userId, String tenantId, String oauthId, String name, String realName, String deptId, String postId, String roleId, String roleName, String avatar, String username, String password, Kv detail, boolean forcePasswordChange, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.userId = userId; this.userId = userId;
this.tenantId = tenantId; this.tenantId = tenantId;
@ -79,6 +83,7 @@ public class BladeUserDetails extends User {
this.roleName = roleName; this.roleName = roleName;
this.avatar = avatar; this.avatar = avatar;
this.detail = detail; this.detail = detail;
this.forcePasswordChange = forcePasswordChange;
} }
} }

@ -83,9 +83,10 @@ public class BladeUserDetailsServiceImpl implements UserDetailsService {
if (Func.isEmpty(userInfo.getRoles())) { if (Func.isEmpty(userInfo.getRoles())) {
throw new UserDeniedAuthorizationException(TokenUtil.USER_HAS_NO_ROLE); throw new UserDeniedAuthorizationException(TokenUtil.USER_HAS_NO_ROLE);
} }
boolean forcePasswordChange = Func.toInt(user.getForcePasswordChange()) == 1;
return new BladeUserDetails(user.getId(), return new BladeUserDetails(user.getId(),
user.getTenantId(), StringPool.EMPTY, user.getName(), user.getRealName(), user.getDeptId(), user.getPostId(), user.getRoleId(), Func.join(result.getData().getRoles()), Func.toStr(user.getAvatar(), TokenUtil.DEFAULT_AVATAR), user.getTenantId(), StringPool.EMPTY, user.getName(), user.getRealName(), user.getDeptId(), user.getPostId(), user.getRoleId(), Func.join(result.getData().getRoles()), Func.toStr(user.getAvatar(), TokenUtil.DEFAULT_AVATAR),
username, AuthConstant.ENCRYPT + user.getPassword(), userInfo.getDetail(), true, true, true, true, username, AuthConstant.ENCRYPT + user.getPassword(), userInfo.getDetail(), forcePasswordChange, true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList(Func.join(result.getData().getRoles()))); AuthorityUtils.commaSeparatedStringToAuthorityList(Func.join(result.getData().getRoles())));
} else { } else {
throw new UsernameNotFoundException(result.getMsg()); throw new UsernameNotFoundException(result.getMsg());

@ -48,6 +48,7 @@ public class BladeJwtTokenEnhancer implements TokenEnhancer {
info.put(TokenUtil.AVATAR, principal.getAvatar()); info.put(TokenUtil.AVATAR, principal.getAvatar());
info.put(TokenUtil.DETAIL, principal.getDetail()); info.put(TokenUtil.DETAIL, principal.getDetail());
info.put(TokenUtil.LICENSE, TokenUtil.LICENSE_NAME); info.put(TokenUtil.LICENSE, TokenUtil.LICENSE_NAME);
info.put(TokenUtil.FORCE_PASSWORD_CHANGE, principal.isForcePasswordChange());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
//token状态设置 //token状态设置

@ -40,6 +40,7 @@ public class TokenUtil {
public final static String DETAIL = TokenConstant.DETAIL; public final static String DETAIL = TokenConstant.DETAIL;
public final static String LICENSE = TokenConstant.LICENSE; public final static String LICENSE = TokenConstant.LICENSE;
public final static String LICENSE_NAME = TokenConstant.LICENSE_NAME; public final static String LICENSE_NAME = TokenConstant.LICENSE_NAME;
public final static String FORCE_PASSWORD_CHANGE = "forcePasswordChange";
public final static String CAPTCHA_HEADER_KEY = "Captcha-Key"; public final static String CAPTCHA_HEADER_KEY = "Captcha-Key";
public final static String CAPTCHA_HEADER_CODE = "Captcha-Code"; public final static String CAPTCHA_HEADER_CODE = "Captcha-Code";

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.NullSerializer; import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.springblade.core.mp.base.BaseEntity; import org.springblade.core.mp.base.BaseEntity;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
@ -93,6 +94,12 @@ public class ExamineItem extends BaseEntity implements Serializable {
*/ */
private String inputMode; private String inputMode;
/**
* 原始记录模板ID
*/
@ApiModelProperty("原始记录模板ID")
private Long templateId;
/** /**
* 使用次数 * 使用次数
*/ */

@ -0,0 +1,32 @@
package org.springblade.lims.entry;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springblade.core.mp.base.BaseEntity;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
@Data
@TableName("t_original_record_template")
public class OriginalRecordTemplate extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@ApiModelProperty("主键")
private Long id;
@ApiModelProperty("模板名称")
private String name;
@ApiModelProperty("关联检测项目ID")
private Long examineItemId;
@ApiModelProperty("业务状态")
private Integer status;
@ApiModelProperty("是否删除 0-否 1-是")
private Integer isDeleted;
}

@ -0,0 +1,29 @@
package org.springblade.lims.entry;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springblade.core.mp.base.BaseEntity;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
@Data
@TableName("t_template_field")
public class TemplateField extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@ApiModelProperty("主键")
private Long id;
@ApiModelProperty("字段名称")
private String fieldName;
@ApiModelProperty("字段类型")
private String fieldType = "text";
@ApiModelProperty("是否删除 0-否 1-是")
private Integer isDeleted = 0;
}

@ -0,0 +1,33 @@
package org.springblade.lims.entry;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 模板-字段关联表实体
* 支持多对多一个模板可包含多个字段一个字段可属于多个模板
*
* @author blade
* @since 2026-06-03
*/
@Data
@TableName("t_template_field_mapping")
public class TemplateFieldMapping implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("主键")
private Long id;
@ApiModelProperty("模板ID")
private Long templateId;
@ApiModelProperty("字段ID")
private Long fieldId;
@ApiModelProperty("模板内排序")
private Integer sortOrder;
}

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity; import org.springblade.core.tenant.mp.TenantEntity;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date; import java.util.Date;
@ -101,4 +102,16 @@ public class User extends TenantEntity {
*/ */
private Integer accountStatus; private Integer accountStatus;
/**
* 是否管理员
*/
@ApiModelProperty(value = "是否管理员 0-否 1-是")
private Integer is_admin;
/**
* 首次登录强制修改密码
*/
@ApiModelProperty(value = "首次登录强制修改密码 0-否 1-是")
private Integer forcePasswordChange;
} }

@ -0,0 +1,45 @@
package org.springblade.lims.controller;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.tool.api.R;
import org.springblade.lims.entry.OriginalRecordTemplate;
import org.springblade.lims.service.IOriginalRecordTemplateService;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@RestController
@AllArgsConstructor
@RequestMapping("/originalRecordTemplate")
@Api(value = "", tags = "")
public class OriginalRecordTemplateController extends BladeController {
private final IOriginalRecordTemplateService originalRecordTemplateService;
@GetMapping("/list")
public R<List<Map<String, Object>>> list() {
return R.data(originalRecordTemplateService.listWithFieldCount());
}
@PostMapping("/save")
public R<OriginalRecordTemplate> save(@RequestBody OriginalRecordTemplate template) {
originalRecordTemplateService.save(template);
return R.data(template);
}
@PostMapping("/update")
public R<OriginalRecordTemplate> update(@RequestBody OriginalRecordTemplate template) {
originalRecordTemplateService.updateById(template);
return R.data(template);
}
@PostMapping("/delete")
public R delete(@RequestParam Long id) {
return R.status(originalRecordTemplateService.deleteLogic(Arrays.asList(id)));
}
}

@ -11,10 +11,14 @@ import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query; import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R; import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.Func;
import org.springblade.lims.entry.ExamineItem;
import org.springblade.lims.entry.Reagent; import org.springblade.lims.entry.Reagent;
import org.springblade.lims.entry.ReagentFormula; import org.springblade.lims.entry.ReagentFormula;
import org.springblade.lims.entry.TemplateField;
import org.springblade.lims.service.IExamineItemService;
import org.springblade.lims.service.IReagentFormulaService; import org.springblade.lims.service.IReagentFormulaService;
import org.springblade.lims.service.IReagentService; import org.springblade.lims.service.IReagentService;
import org.springblade.lims.service.ITemplateFieldService;
import org.springblade.lims.utils.FormulaValidationTool; import org.springblade.lims.utils.FormulaValidationTool;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -39,6 +43,8 @@ public class ReagentFormulaController extends BladeController {
private final IReagentFormulaService service; private final IReagentFormulaService service;
private final IReagentService reagentService; private final IReagentService reagentService;
private final IExamineItemService examineItemService;
private final ITemplateFieldService templateFieldService;
/** /**
* 分页查询 * 分页查询
@ -120,20 +126,44 @@ public class ReagentFormulaController extends BladeController {
/** /**
* 获取试剂关联的可用变量 * 获取试剂关联的可用变量
* Reagent.resultDeterminationMethod 中语义提取 * 优先检验项模板字段中获取无模板时回退到 Reagent.resultDeterminationMethod 解析
*/ */
@GetMapping("/variables") @GetMapping("/variables")
@ApiOperation(value = "获取可用变量", notes = "根据试剂ID获取公式可用变量") @ApiOperation(value = "获取可用变量", notes = "根据试剂ID获取公式可用变量")
public R getVariables(@ApiParam(value = "试剂ID") @RequestParam Long reagentId) { public R<List<String>> getVariables(@ApiParam(value = "试剂ID") @RequestParam Long reagentId) {
Reagent reagent = reagentService.getById(reagentId); // 1. 查找所有使用该试剂的检验项(reagentId 为逗号分隔字符串,使用 LIKE 匹配)
if (reagent == null) { LambdaQueryWrapper<ExamineItem> wrapper = new LambdaQueryWrapper<>();
return R.data(new ArrayList<>()); wrapper.like(ExamineItem::getReagentId, reagentId.toString());
List<ExamineItem> items = examineItemService.list(wrapper);
// 2. 从有模板的检验项中收集模板字段名
Set<String> variables = new LinkedHashSet<>();
boolean hasTemplateFields = false;
for (ExamineItem item : items) {
if (item.getTemplateId() != null) {
List<TemplateField> fields = templateFieldService.getFieldsByTemplateId(item.getTemplateId());
if (fields != null && !fields.isEmpty()) {
hasTemplateFields = true;
for (TemplateField field : fields) {
variables.add(field.getFieldName());
}
}
}
} }
// 3. 无模板字段时回退到旧逻辑:从 resultDeterminationMethod 解析
if (!hasTemplateFields) {
Reagent reagent = reagentService.getById(reagentId);
if (reagent != null) {
String method = reagent.getResultDeterminationMethod(); String method = reagent.getResultDeterminationMethod();
List<Map<String, Object>> variables = extractVariables(method); List<Map<String, Object>> oldVars = extractVariables(method);
for (Map<String, Object> v : oldVars) {
variables.add((String) v.get("name"));
}
}
}
return R.data(variables); return R.data(new ArrayList<>(variables));
} }
/** /**

@ -0,0 +1,55 @@
package org.springblade.lims.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.tool.api.R;
import org.springblade.lims.entry.TemplateField;
import org.springblade.lims.service.ITemplateFieldService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@AllArgsConstructor
@RequestMapping("/templateField")
@Api(value = "", tags = "")
public class TemplateFieldController extends BladeController {
private final ITemplateFieldService templateFieldService;
@GetMapping("/list")
public R<List<TemplateField>> list(Long templateId) {
return R.data(templateFieldService.getFieldsByTemplateId(templateId));
}
@PostMapping("/save")
public R<TemplateField> save(@RequestBody TemplateField field) {
templateFieldService.saveField(field);
return R.data(field);
}
@PostMapping("/update")
public R update(@RequestBody TemplateField field) {
return R.status(templateFieldService.updateField(field));
}
@PostMapping("/delete")
public R delete(Long id) {
return R.status(templateFieldService.deleteField(id));
}
@PostMapping("/reorder")
public R reorder(@RequestBody List<TemplateField> fields) {
return R.status(templateFieldService.reorderFields(fields));
}
@PostMapping("/bindTemplate")
public R bindTemplate(
@ApiParam(value = "模板ID", required = true) @RequestParam Long templateId,
@RequestBody List<Long> fieldIds) {
return R.status(templateFieldService.bindTemplateFields(templateId, fieldIds));
}
}

@ -0,0 +1,8 @@
package org.springblade.lims.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.lims.entry.OriginalRecordTemplate;
public interface OriginalRecordTemplateMapper extends BaseMapper<OriginalRecordTemplate> {
}

@ -0,0 +1,13 @@
package org.springblade.lims.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.lims.entry.TemplateField;
/**
* @author swj
* @since 2022年6月2日15:47:39
*/
public interface TemplateFieldMapper extends BaseMapper<TemplateField> {
}

@ -0,0 +1,13 @@
package org.springblade.lims.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.lims.entry.TemplateFieldMapping;
/**
* 模板-字段关联表 Mapper
*
* @author blade
* @since 2026-06-03
*/
public interface TemplateFieldMappingMapper extends BaseMapper<TemplateFieldMapping> {
}

@ -0,0 +1,21 @@
package org.springblade.lims.service;
import org.springblade.core.mp.base.BaseService;
import org.springblade.lims.entry.OriginalRecordTemplate;
import java.util.List;
import java.util.Map;
/**
* @author swj
* @since 2026年6月3日
*/
public interface IOriginalRecordTemplateService extends BaseService<OriginalRecordTemplate> {
/**
* 查询所有原始记录模板含字段数量
*
* @return 模板列表 fieldCount
*/
List<Map<String, Object>> listWithFieldCount();
}

@ -0,0 +1,64 @@
package org.springblade.lims.service;
import org.springblade.core.mp.base.BaseService;
import org.springblade.lims.entry.TemplateField;
import java.util.List;
/**
* @author swj
* @since 2022年6月2日15:47:39
*/
public interface ITemplateFieldService extends BaseService<TemplateField> {
/**
* 根据模板ID获取字段列表
*
* @param templateId 模板ID
* @return 字段列表
*/
List<TemplateField> getFieldsByTemplateId(Long templateId);
/**
* 保存字段
*
* @param field 字段实体
* @return 是否成功
*/
boolean saveField(TemplateField field);
/**
* 更新字段
*
* @param field 字段实体
* @return 是否成功
*/
boolean updateField(TemplateField field);
/**
* 删除字段
*
* @param id 字段ID
* @return 是否成功
*/
boolean deleteField(Long id);
/**
* 重新排序字段
*
* @param fields 字段列表含更新后的排序号
* @return 是否成功
*/
boolean reorderFields(List<TemplateField> fields);
/**
* 绑定字段到模板
*
* @param templateId 模板ID
* @param fieldIds 字段ID列表
* @return 是否成功
*/
boolean bindTemplateFields(Long templateId, List<Long> fieldIds);
}

@ -0,0 +1,57 @@
package org.springblade.lims.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.lims.entry.OriginalRecordTemplate;
import org.springblade.lims.entry.TemplateField;
import org.springblade.lims.mapper.OriginalRecordTemplateMapper;
import org.springblade.lims.mapper.TemplateFieldMapper;
import org.springblade.lims.service.IOriginalRecordTemplateService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author swj
* @since 2026年6月3日
*/
@Service
@AllArgsConstructor
public class OriginalRecordTemplateServiceImpl extends BaseServiceImpl<OriginalRecordTemplateMapper, OriginalRecordTemplate> implements IOriginalRecordTemplateService {
private final OriginalRecordTemplateMapper originalRecordTemplateMapper;
private final TemplateFieldMapper templateFieldMapper;
@Override
public List<Map<String, Object>> listWithFieldCount() {
// Query all non-deleted templates
LambdaQueryWrapper<OriginalRecordTemplate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OriginalRecordTemplate::getIsDeleted, 0)
.orderByAsc(OriginalRecordTemplate::getCreateTime);
List<OriginalRecordTemplate> templates = originalRecordTemplateMapper.selectList(queryWrapper);
// For each template, return basic info with real field count
List<Map<String, Object>> result = new ArrayList<>();
for (OriginalRecordTemplate tpl : templates) {
Map<String, Object> item = new HashMap<>();
item.put("id", tpl.getId());
item.put("name", tpl.getName());
item.put("examineItemId", tpl.getExamineItemId());
// Query actual field count for this template via mapping table
Integer fieldCount = templateFieldMapper.selectCount(
new LambdaQueryWrapper<TemplateField>()
.inSql(TemplateField::getId,
"SELECT m.field_id FROM t_template_field_mapping m WHERE m.template_id = " + tpl.getId()
)
.eq(TemplateField::getIsDeleted, 0)
);
item.put("fieldCount", fieldCount);
result.add(item);
}
return result;
}
}

@ -0,0 +1,99 @@
package org.springblade.lims.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.AllArgsConstructor;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.lims.entry.TemplateField;
import org.springblade.lims.entry.TemplateFieldMapping;
import org.springblade.lims.mapper.TemplateFieldMapper;
import org.springblade.lims.mapper.TemplateFieldMappingMapper;
import org.springblade.lims.service.ITemplateFieldService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author swj
* @since 2022年6月2日15:53:01
*/
@Service
@AllArgsConstructor
public class TemplateFieldServiceImpl extends BaseServiceImpl<TemplateFieldMapper, TemplateField> implements ITemplateFieldService {
private final TemplateFieldMappingMapper templateFieldMappingMapper;
@Override
public List<TemplateField> getFieldsByTemplateId(Long templateId) {
if (templateId == null) {
// 字段库:返回所有未删除的字段(多对多设计下,一个字段可同时属于多个模板)
LambdaQueryWrapper<TemplateField> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TemplateField::getIsDeleted, 0);
return baseMapper.selectList(queryWrapper);
}
// 按模板查询:通过关联表 JOIN 获取字段列表
LambdaQueryWrapper<TemplateField> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TemplateField::getIsDeleted, 0);
queryWrapper.inSql(TemplateField::getId,
"SELECT m.field_id FROM t_template_field_mapping m WHERE m.template_id = " + templateId + " ORDER BY m.sort_order ASC"
);
return baseMapper.selectList(queryWrapper);
}
@Override
public boolean saveField(TemplateField field) {
return baseMapper.insert(field) > 0;
}
@Override
public boolean updateField(TemplateField field) {
return baseMapper.updateById(field) > 0;
}
@Override
public boolean deleteField(Long id) {
TemplateField field = new TemplateField();
field.setId(id);
field.setIsDeleted(1);
// 同时清除关联表中的关联记录
LambdaUpdateWrapper<TemplateFieldMapping> clearMapping = new LambdaUpdateWrapper<>();
clearMapping.eq(TemplateFieldMapping::getFieldId, id);
templateFieldMappingMapper.delete(clearMapping);
return baseMapper.updateById(field) > 0;
}
@Override
public boolean reorderFields(List<TemplateField> fields) {
// 排序逻辑已移至关联表(t_template_field_mapping.sort_order),
// 此方法保留以保持向后兼容,前端暂未使用
return true;
}
@Override
public boolean bindTemplateFields(Long templateId, List<Long> fieldIds) {
// 1. 清除该模板下的所有字段关联
LambdaUpdateWrapper<TemplateFieldMapping> clearWrapper = new LambdaUpdateWrapper<>();
clearWrapper.eq(TemplateFieldMapping::getTemplateId, templateId);
templateFieldMappingMapper.delete(clearWrapper);
// 2. 绑定新字段(按 fieldIds 数组顺序生成 sort_order)
if (fieldIds != null && !fieldIds.isEmpty()) {
List<TemplateFieldMapping> mappings = fieldIds.stream()
.map(fieldId -> {
TemplateFieldMapping mapping = new TemplateFieldMapping();
mapping.setTemplateId(templateId);
mapping.setFieldId(fieldId);
mapping.setSortOrder(fieldIds.indexOf(fieldId));
return mapping;
})
.collect(Collectors.toList());
for (TemplateFieldMapping mapping : mappings) {
templateFieldMappingMapper.insert(mapping);
}
}
return true;
}
}

@ -0,0 +1,116 @@
package org.springblade.labuser.init;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.constant.BladeConstant;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.system.feign.ISysClient;
import org.springblade.system.user.entity.User;
import org.springblade.system.user.service.IUserService;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.Random;
/**
* 应用启动时自动检查并创建备用管理员账户
* <p>
* blade_user 表中 is_admin=1 role_id 匹配超级管理员角色的账户数小于2时
* 自动创建一个备用管理员账户并生成随机6位数字密码密码会输出到应用日志
*
* @author labx
*/
@Component
@AllArgsConstructor
@Slf4j
public class AdminAccountInitializer implements ApplicationRunner {
private static final String BACKUP_ADMIN_ACCOUNT = "admin_backup";
private static final String ADMIN_NAME = "备用管理员";
private static final String ROLE_ALIAS_ADMINISTRATOR = "admin";
private static final String ADMIN_TENANT_ID = "704067";
private static final int MIN_ADMIN_COUNT = 2;
private static final int PASSWORD_LENGTH = 6;
private final IUserService userService;
private final ISysClient sysClient;
@Override
public void run(ApplicationArguments args) {
try {
// 1. 获取超级管理员角色ID
String adminTenantId = ADMIN_TENANT_ID;
R<String> roleResult = sysClient.getRoleIdByAlias(adminTenantId, ROLE_ALIAS_ADMINISTRATOR);
if (!roleResult.isSuccess() || roleResult.getData() == null) {
log.warn("AdminAccountInitializer: 无法获取超级管理员角色ID,跳过初始化");
return;
}
String adminRoleId = roleResult.getData();
// 2. 检查是否已存在备用管理员账户(防重入)
int existingCount = userService.count(
Wrappers.<User>query().lambda()
.eq(User::getAccount, BACKUP_ADMIN_ACCOUNT)
.eq(User::getTenantId, adminTenantId)
);
if (existingCount > 0) {
log.info("备用管理员账户 [{}] 已存在,跳过创建", BACKUP_ADMIN_ACCOUNT);
return;
}
// 3. 统计当前超级管理员数量
int adminCount = userService.count(
Wrappers.<User>query().lambda()
.eq(User::getIs_admin, 1)
.eq(User::getRoleId, adminRoleId)
);
if (adminCount >= MIN_ADMIN_COUNT) {
log.info("当前管理员账户数量 {} 已满足要求(≥{}),跳过创建", adminCount, MIN_ADMIN_COUNT);
return;
}
// 4. 创建备用管理员账户
String password = generateRandomPassword(PASSWORD_LENGTH);
User newAdmin = new User();
newAdmin.setTenantId(adminTenantId);
newAdmin.setAccount(BACKUP_ADMIN_ACCOUNT);
newAdmin.setName(ADMIN_NAME);
newAdmin.setRealName(ADMIN_NAME);
newAdmin.setPassword(DigestUtil.encrypt(password));
newAdmin.setRoleId(adminRoleId);
newAdmin.setIs_admin(1);
newAdmin.setForcePasswordChange(1);
newAdmin.setAccountStatus(1);
boolean saved = userService.save(newAdmin);
if (saved) {
log.info("管理员账户 [{}] 已创建,密码:{}", BACKUP_ADMIN_ACCOUNT, password);
} else {
log.warn("管理员账户 [{}] 创建失败(save返回false)", BACKUP_ADMIN_ACCOUNT);
}
} catch (Exception e) {
log.error("AdminAccountInitializer: 初始化备用管理员账户异常", e);
}
}
/**
* 生成随机纯数字密码
*
* @param length 密码长度
* @return 纯数字密码字符串
*/
private String generateRandomPassword(int length) {
Random random = new Random();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append(random.nextInt(10));
}
return sb.toString();
}
}

@ -5,6 +5,7 @@ import org.springblade.core.cloud.feign.EnableBladeFeign;
import org.springblade.core.launch.BladeApplication; import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant; import org.springblade.core.launch.constant.AppConstant;
import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableAsync;
/** /**
@ -15,6 +16,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
@EnableBladeFeign @EnableBladeFeign
@SpringCloudApplication @SpringCloudApplication
@EnableAsync @EnableAsync
@ComponentScan({"org.springblade.system.user", "org.springblade.labuser"})
public class UserApplication { public class UserApplication {
public static void main(String[] args) { public static void main(String[] args) {

@ -113,6 +113,18 @@ public class UserController {
return R.data(UserWrapper.build().pageVO(pages)); return R.data(UserWrapper.build().pageVO(pages));
} }
/**
* 管理员列表
*/
@GetMapping("/admin/list")
@ApiOperationSupport(order = 3)
@ApiOperation(value = "管理员列表", notes = "查询管理员用户")
//@PreAuth(RoleConstant.HAS_ROLE_ADMIN)
public R<IPage<User>> adminList(User user, Query query) {
IPage<User> pages = userService.getAdminList(Condition.getPage(query), user);
return R.data(pages);
}
/** /**
* 获取部门下角色用户列表 * 获取部门下角色用户列表
*/ */

@ -214,4 +214,13 @@ public interface IUserService extends BaseService<User> {
* @return * @return
*/ */
public List<User> listRolebyId(String rolename); public List<User> listRolebyId(String rolename);
/**
* 管理员列表分页
*
* @param page
* @param user
* @return
*/
IPage<User> getAdminList(IPage<User> page, User user);
} }

@ -544,5 +544,12 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
return null; return null;
} }
@Override
public IPage<User> getAdminList(IPage<User> page, User user) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getIs_admin, 1);
return baseMapper.selectPage(page, queryWrapper);
}
} }

@ -46,9 +46,11 @@ public class RedisKeyExpirationListener extends KeyExpirationEventMessageListene
String expiredKey = message.toString(); String expiredKey = message.toString();
// System.out.println("缓存过期的key为:" + expiredKey); // System.out.println("缓存过期的key为:" + expiredKey);
LambdaQueryWrapper<Train> queryWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Train> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Train::getId,expiredKey); queryWrapper.eq(Train::getId, expiredKey);
Train train = trainService.getOne(queryWrapper); Train train = trainService.getOne(queryWrapper);
System.out.println(train.getName()); if (train == null) {
return;
}
//这里可以根据培训的id查询到培训的具体信息,然后再实现通知功能 //这里可以根据培训的id查询到培训的具体信息,然后再实现通知功能
//获取培训人员信息并发消息 //获取培训人员信息并发消息
LambdaQueryWrapper<TrainPerson> personWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<TrainPerson> personWrapper = new LambdaQueryWrapper<>();
@ -62,6 +64,9 @@ public class RedisKeyExpirationListener extends KeyExpirationEventMessageListene
LambdaQueryWrapper<TrainSpeak> speakWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<TrainSpeak> speakWrapper = new LambdaQueryWrapper<>();
speakWrapper.eq(TrainSpeak::getTrainId, train.getId()); speakWrapper.eq(TrainSpeak::getTrainId, train.getId());
TrainSpeak teacher = trainSpeakService.getOne(speakWrapper); TrainSpeak teacher = trainSpeakService.getOne(speakWrapper);
if (teacher == null) {
return;
}
messageClient.event(SysTypeEnum.INFORM.getValue(), "会议提醒", messageClient.event(SysTypeEnum.INFORM.getValue(), "会议提醒",
"您有新的会议将在" + train.getDuration() + "分钟后开始,请准时参加!", 1, 5, teacher.getSpeakName(), "/train/project"); "您有新的会议将在" + train.getDuration() + "分钟后开始,请准时参加!", 1, 5, teacher.getSpeakName(), "/train/project");
} }

Loading…
Cancel
Save