feat: 用户启停/注销、机构账号查询、手机号查记录、机构上传配置

1. 用户管理:新增启用(user/enable)/禁用(user/disable)/注销(user/admin_cancel)接口,
   平台超管可操作全部用户,机构管理员限本机构;注销带分布式锁、禁自己/超管守卫
2. 禁用/注销立即踢下线:Redis黑名单(user:disabled:{uid}) + JWT过滤器拦截,
   TTL与token有效期一致
3. 系统管理员查看各机构账号:GET user/page/org_account(机构/账号/手机号等筛选)
4. 按手机号查上传/发送/接收记录:GET user_file/record/page_by_phone,
   发送/接收方向按部署侧 file.transfer.location 自动映射
5. 机构上传配置:新表 x_upload_config + upload_config/info|modify|check 接口,
   上传总开关/后缀白名单/单文件大小限制,网盘落库时强制校验
6. 加固:UserInfoDTO.setPassword 缺省密码未配置时不再抛 rawPassword cannot be null
7. 新增 x_upload_config 建表脚本(upgrade-20260915-release.sql 为上线版)、
   生产部署说明、前端接口文档×2、运维脚本 sfile-ctl.sh、AGENTS.md
This commit is contained in:
2026-09-15 12:14:27 +08:00
parent 54b7e9a555
commit c305ca946e
39 changed files with 2637 additions and 8 deletions

View File

@@ -4,8 +4,11 @@ import com.senmesh.common.constant.CommonConstants;
import com.senmesh.common.exception.util.Assert;
import com.senmesh.common.util.ListUtil;
import com.senmesh.common.util.UserContextHolder;
import com.senmesh.component.file.config.FileProperties;
import com.senmesh.component.file.service.FileService;
import com.senmesh.component.file.util.FileUtils;
import com.senmesh.sfile.constant.FileLocationEnum;
import com.senmesh.sfile.constant.FileRecordTypeEnum;
import com.senmesh.sfile.constant.FileStatusEnum;
import com.senmesh.sfile.constant.FileTypeEnum;
import com.senmesh.sfile.constant.RootTypeEnum;
@@ -18,9 +21,12 @@ import com.senmesh.sfile.dto.UserFileDTO;
import com.senmesh.sfile.dto.UserFileModifyDTO;
import com.senmesh.sfile.dto.UserFileModifyStatusDTO;
import com.senmesh.sfile.dto.UserFileQueryDTO;
import com.senmesh.sfile.dto.UserFileRecordDTO;
import com.senmesh.sfile.dto.UserFileRecordQueryDTO;
import com.senmesh.sfile.dto.UserFileStatisticsDTO;
import com.senmesh.sfile.entity.UserFile;
import com.senmesh.sfile.service.UserFileService;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -30,9 +36,11 @@ import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 用户文件ServiceImpl
@@ -47,6 +55,9 @@ public class UserFileServiceImpl implements UserFileService {
@Autowired
private FileService fileService;
@Autowired
private FileProperties fileProperties;
@Override
@Transactional
public UserFileDTO create(UserFileCreateDTO createDTO) {
@@ -346,5 +357,85 @@ public class UserFileServiceImpl implements UserFileService {
queryDTO.disablePage();
return userFileMapper.statisticsByCategory(queryDTO);
}
@Override
public void pageRecordByPhone(UserFileRecordQueryDTO queryDTO) {
Assert.isTrue(UserContextHolder.isSuperior(), "当前用户无操作权限");
Assert.notBlank(queryDTO.getPhone(), "手机号不能为空");
queryDTO.setPhone(queryDTO.getPhone().trim());
if (queryDTO.getRecordType() != null) {
Assert.notNull(FileRecordTypeEnum.getByType(queryDTO.getRecordType()),
"记录类型不合法,仅支持1上传/2发送/3接收");
}
queryDTO.setRootTypeIn(resolveRootTypes(queryDTO.getRecordType()));
List<UserFileRecordDTO> records = userFileMapper.listRecordByPhone(queryDTO);
fillRecordType(records);
}
/**
* 记录类型 -> root_type 条件
* 上传记录固定为普通(1);发送/接收方向与本端部署位置有关:
* 内网侧发送为内穿外(2)、接收为外传内(3);外网侧相反
*
* @param recordType 记录类型,为空查全部
*/
private Set<String> resolveRootTypes(Integer recordType) {
if (recordType == null) {
return Sets.newHashSet(RootTypeEnum.NORMAL.getType(), getSendRootType(), getReceiveRootType());
}
switch (FileRecordTypeEnum.getByType(recordType)) {
case UPLOAD:
return Collections.singleton(RootTypeEnum.NORMAL.getType());
case SEND:
return Collections.singleton(getSendRootType());
case RECEIVE:
return Collections.singleton(getReceiveRootType());
default:
return resolveRootTypes(null);
}
}
/**
* 本端发送方向:内网侧为内穿外(2),外网侧为外传内(3),与TransferUserFileServiceImpl保持一致
*/
private String getSendRootType() {
return FileLocationEnum.isInternal(fileProperties.getTransfer().getLocation())
? RootTypeEnum.INTERNAL_TO_EXTERNAL.getType()
: RootTypeEnum.EXTERNAL_TO_INTERNAL.getType();
}
/**
* 本端接收方向:内网侧为外传内(3),外网侧为内穿外(2),与TransferFileMonitorServiceImpl保持一致
*/
private String getReceiveRootType() {
return FileLocationEnum.isInternal(fileProperties.getTransfer().getLocation())
? RootTypeEnum.EXTERNAL_TO_INTERNAL.getType()
: RootTypeEnum.INTERNAL_TO_EXTERNAL.getType();
}
/**
* 按本端部署位置回填记录类型与描述
*/
private void fillRecordType(List<UserFileRecordDTO> records) {
if (records == null || records.isEmpty()) {
return;
}
for (UserFileRecordDTO record : records) {
String rootType = record.getRootType();
if (RootTypeEnum.NORMAL.getType().equals(rootType)) {
record.setRecordType(FileRecordTypeEnum.UPLOAD.getType());
} else if (getSendRootType().equals(rootType)) {
record.setRecordType(FileRecordTypeEnum.SEND.getType());
} else if (getReceiveRootType().equals(rootType)) {
record.setRecordType(FileRecordTypeEnum.RECEIVE.getType());
}
if (record.getRecordType() != null) {
record.setRecordTypeDesc(FileRecordTypeEnum.getByType(record.getRecordType()).getDesc());
} else {
RootTypeEnum rootTypeEnum = RootTypeEnum.getByType(rootType);
record.setRecordTypeDesc(rootTypeEnum == null ? rootType : rootTypeEnum.getDesc());
}
}
}
}