# 1.后端管理平台文档

### 一、框架代码

#### 1.实例化函数

```java
private final AliyunImsService aliyunImsService;
```

不采用注解是因为测试用例不方便引用

### satoken

官方文档：

[https://sa-token.cc](https://sa-token.cc/)

**Sa-Token** 是一个轻量级 Java 权限认证框架，主要解决：**登录认证**、**权限认证**、**单点登录**、**OAuth2.0**、**分布式Session会话**、**微服务网关鉴权** 等一系列权限相关问题。

#### 登录认证

**1.获取当前token登录人信息**

```java
LoginUser loginUser = getLoginUser();
```

### mybatisplus

#### 1.把list转换为分页page：

```java
/**
 * 分页工具
 *
 * @author renq@trasen.cn
 */	
public class PageUtils {
    public static <T> IPage<T> listToPage(int pageNo, int pageSize, List<T> list) {
        List<T> pageList = new ArrayList<>();
        int size = list.size();
        if (pageSize > size) {
            pageSize = size;
        }
        // 求出最大页数，防止currentPage越界
        int maxPage = size % pageSize == 0 ? size / pageSize : size / pageSize + 1;
        if (pageNo > maxPage) {
            pageNo = maxPage;
        }
        // 当前页第一条数据下标
        int curIdx = pageNo > 1 ? (pageNo - 1) * pageSize : 0;

        // 将当前页的数据放进pageList
        for (int i = 0; i < pageSize && curIdx + i < list.size(); i++) {
            pageList.add(list.get(curIdx + i));
        }
        IPage<T> page = new Page<>(pageNo, pageSize);
        page.setRecords(pageList);
        page.setTotal(list.size());
        return page;
    }
}
```

引用方法把sysuser的list变成ipage

```java
IPage<SysUser> iPage = PageUtils.listToPage(pageQuery.getPageNo(), pageQuery.getPageSize(), sysUsers);
```

#### 2.LambdaQueryWrapper查询

```java
LambdaQueryWrapper<ScaleInfo> lqw = new LambdaQueryWrapper<>();
        lqw.eq(StringUtils.isNotBlank(record.getIsDeleted()), ScaleInfo::getIsDeleted, record.getIsDeleted())
            .like(StringUtils.isNotBlank(record.getScaleName()), ScaleInfo::getScaleName, record.getScaleName())
            .like(StringUtils.isNotBlank(record.getScaleCategory()), ScaleInfo::getScaleCategory, record.getScaleCategory())
            .orderByDesc(ScaleInfo::getCreateTime);
Page<ScaleInfo> page = baseMapper.selectPage(pageQuery.build(), lqw);
```

#### 3.一行简单查询

```java
List<SysUser> sysUsers = userMapper.selectList(new LambdaQueryWrapper<SysUser>().eq(SysUser::getDeptId, deptId).eq(SysUser::getStatus, "0"));
```

#### 4.更新某一个字段

```java
//完成状态变化
LambdaUpdateWrapper<UserScaleGroup> updateWrapperGroup = new LambdaUpdateWrapper<>();
updateWrapperGroup.set(UserScaleGroup::getStatus, "1")
    .set(UserScaleGroup::getReportId, scaleReport.getId())
    .eq(UserScaleGroup::getGroupInfoId, record.getGroupId())
    .eq(UserScaleGroup::getUserId, String.valueOf(sysUser.getUserId()))
    .eq(UserScaleGroup::getScaleId, record.getSurveyId());
userScaleGroupMapper.update(null, updateWrapperGroup);
```

#### 5.分页查询之后，取出page类中的record部门形成list列表

```java
LambdaQueryWrapper<ScaleReport> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(record.getIsDeleted()), ScaleReport::getIsDeleted, record.getIsDeleted())
        .eq(ObjectUtil.isNotNull(record.getDeptId()), ScaleReport::getDeptId, record.getDeptId())
        .eq(StringUtils.isNotBlank(record.getUserName()), ScaleReport::getUserName, record.getUserName())
        .like(StringUtils.isNotBlank(record.getScaleName()), ScaleReport::getScaleName, record.getScaleName())
        .like(StringUtils.isNotBlank(record.getNickName()), ScaleReport::getNickName, record.getNickName())
        .between((record.getBeginTime() != null && record.getEndTime() != null), ScaleReport::getEndAssessmentDate, record.getBeginTime(), record.getEndTime())
        .orderByDesc(ScaleReport::getEndAssessmentDate);

Page<ScaleReport> page = reportMapper.selectPage(pageQuery.build(), lqw);
List<ScaleReport> scaleReports = page.getRecords();
```

#### 6.数据权限结合LambdaQueryWrapper

```java
        LambdaQueryWrapper<ReportInfo> lqw = new LambdaQueryWrapper<>();
        //查询条件：逻辑删除、标题
        lqw.eq(ReportInfo::getIsDeleted, "0")
            .like(StringUtils.isNotBlank(record.getReportName()), ReportInfo::getReportName, record.getReportName())
            .orderByDesc(ReportInfo::getCreateTime);
        Page<ReportInfo> reportInfos = reportInfoMapper.selectReportPage(pageQuery.build(),lqw);
```

```java
    /**
     * 查询报告配置列表
     */
    @DataPermission({
            @DataColumn(key = "deptName", value = "u.dept_id")
    })
    Page<ReportInfo> selectReportPage(@Param("page") Page<ReportInfo> page,@Param(Constants.WRAPPER) Wrapper<ReportInfo> lqw);
```

```java
<select id="selectReportPage" resultType="cn.trasen.lab.tongue.domain.ReportInfo">
        SELECT *
        FROM report_info r left join sys_user u on r.create_by = u.user_name
        ${ew.getCustomSqlSegment}
    </select>
```

#### 大小基础比较判断

| 方法      | 含义   | SQL  |
| ------- | ---- | ---- |
| `.eq()` | 等于   | `=`  |
| `.ne()` | 不等于  | `!=` |
| `.gt()` | 大于   | `>`  |
| `.ge()` | 大于等于 | `>=` |
| `.lt()` | 小于   | `<`  |
| `.le()` | 小于等于 | `<=` |

```java
Wrappers.<MarketingDevice>lambdaQuery()
    .eq(MarketingDevice::getUserId, userId)
    .ge(MarketingDevice::getDeviceStatus, 1)
```

### 类型转换

**1.把类列表变成字符串列表**

```java
List<ScaleGroupDept> scaleGroupDepts  //类列表
List<String> deptNames = scaleGroupDepts.stream()
    .map(ScaleGroupDept::getDeptName)
    .collect(Collectors.toList());
```

**2.把字符串数组变为集合**

```java
    /**
     * 部门组
     */
    @TableField(exist = false)
    private String[] deptIds;

List<String> deptList = Arrays.asList(record.getDeptIds());
```

**3.前端传参时间格式的字符串转换**

```java
    /**
     * 最后面访时间结束
     */
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date lastInterviewDateEnd;
```

**4.字符串string类型的时间转换为date类型**

```java
```

**5.复制类**

**使用mapstruct**

```java
@Mapper(componentModel = "spring")
public interface YmxtConverter {

    /**
     * 将 DailyTask 转换为 DailyTaskVo
     *
     * @param dailyTask DailyTask 对象
     * @return DailyTaskVo 对象
     */
    @Mapping(source = "dailyTask.specialty", target = "specialization")
    DailyTaskVo dailyTaskToVoConvert(DailyTask dailyTask);

}

//使用时候
@Resource
private YmxtConverter ymxtConverter;

DailyTaskVo dailyTaskVo = ymxtConverter.dailyTaskToVoConvert(dailyTask);
```

**使用mapstruct-plus**

```java
Course course = MapstructUtils.convert(courseVo, Course.class);

@AutoMapper(target = Course.class, reverseConvertGenerate = false)
public class CourseVo extends QueryEntity {
}
```

**指定转换方法**

当某个属性需要单独定义转换逻辑，并且比较复杂时，可以先实现该方法，通过 `qualifiedByName` 来指定该方法。

```java
@Component
@Named("TitleTranslator")
public class Titles {

    @Named("EnglishToFrench")
    public String translateTitleEF(String title) {
        if ("One Hundred Years of Solitude".equals(title)) {
            return "Cent ans de solitude";
        }
        return "Inconnu et inconnu";
    }

    @Named("FrenchToEnglish")
    public String translateTitleFE(String title) {
        if ("Cent ans de solitude".equals(title)) {
            return "One Hundred Years of Solitude";
        }
        return "Unknown";
    }

}
```

接下来应用该转换逻辑：

```java
@Data
@AutoMapper(target = FrenchRelease.class, uses = Titles.class)
public class EnglishRelease {

    @AutoMapping(qualifiedByName = "EnglishToFrench")
    private String title;

}

```

```java
@Data
@AutoMapper(target = EnglishRelease.class, uses = Titles.class)
public class FrenchRelease {

    @AutoMapping(qualifiedByName = "FrenchToEnglish")
    private String title;

}

```

### 三、时间相关

**1.获取当前时间的年月：**

```java
// 获取当前日期
LocalDate now = LocalDate.now();
// 设置日期格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
String formattedDate = now.format(formatter);
```

#### 2.前端传参时间，后端自动转换

@JsonFormat： 是 Jackson 库提供的注解（依赖 jackson-databind），用于控制 Java 对象与 JSON 字符串之间的序列化（Java 对象 → JSON）和反序列化（JSON → Java 对象）时的日期格式。

@DateTimeFormat ：是 Spring 提供的注解，用于处理 HTTP 请求参数（如 @RequestParam、@PathVariable 或表单数据）中的日期字符串，解析为 Java 日期对象（如 LocalDateTime、Date）。

**两者的区别与结合使用**

| 特性       | @JsonFormat                      | @DateTimeFormat         |
| -------- | -------------------------------- | ----------------------- |
| **所属库**  | Jackson (jackson-databind)       | Spring (spring-web)     |
| **主要作用** | 控制 JSON 序列化/反序列化的日期格式            | 解析 HTTP 请求参数为 Java 日期对象 |
| **适用场景** | REST API 的 JSON 输入/输出            | 控制器接收查询参数、表单或 JSON 请求体  |
| **作用范围** | 实体类字段（如 PublicInfo 的 createTime） | 控制器参数或 DTO 字段           |
| **示例格式** | yyyy-MM-dd HH:mm:ss              | yyyy-MM-dd HH:mm:ss     |
| **时区支持** | 支持 timezone 属性（如 Asia/Shanghai）  | 默认使用系统时区，需结合 JVM 时区设置   |

```java
public class PublicInfoVo {

    /**
     * 公开团测主表id
     */
    private Long publicInfoId;

    /**
     * 计划名称
     */
    @NotBlank(message = "计划名称不能为空")
    private String publicName;

    /**
     * 测评开始时间
     */
    @NotBlank(message = "测评开始时间不能为空")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private Date startTime;

    /**
     * 测评结束时间
     */
    @NotBlank(message = "测评结束时间不能为空")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private Date endTime;
    
    /**
     * 逻辑删除：0启用 1停用
     */
    private String isDeleted;
}
```

### 四、分页查询

**1.先分页之后再修改部分字段**

```java
LambdaQueryWrapper<ScaleInfo> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(record.getIsDeleted()), ScaleInfo::getIsDeleted, record.getIsDeleted())
    .like(StringUtils.isNotBlank(record.getScaleName()), ScaleInfo::getScaleName, record.getScaleName())
    .like(StringUtils.isNotBlank(record.getScaleCategory()), ScaleInfo::getScaleCategory, record.getScaleCategory())
    .orderByDesc(ScaleInfo::getCreateTime);
Page<ScaleInfo> page = baseMapper.selectPage(pageQuery.build(), lqw);        
//添加量表类型字段
for (ScaleInfo scaleInfo : page.getRecords()) {
    String scaleId = scaleInfo.getId();

    //查询量表对应的字段
    List<String> typeNames = scaleInfoTypeMapper.selectTypeNameList(scaleId);
    String typeName = String.join(";", typeNames);
    scaleInfo.setScaleTypeIs(typeName);
}
return TableDataInfo.build(page);
```

分页之后拿到records，然后再遍历修改所有的数据，也可以如下写法

```java
//查询团测主表相关数据
LambdaQueryWrapper<ScaleGroupInfo> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(record.getStatus()), ScaleGroupInfo::getStatus, record.getStatus())
    .like(StringUtils.isNotBlank(record.getGroupName()), ScaleGroupInfo::getGroupName, record.getGroupName()).orderByDesc(ScaleGroupInfo::getCreateTime);
Page<ScaleGroupInfo> scaleGroupInfoPage = baseMapper.selectPage(pageQuery.build(), lqw);
List<ScaleGroupInfo> scaleGroupInfos = scaleGroupInfoPage.getRecords();
```

### 五、定时任务

#### 流数据处理

1.集合类中抽出其中一个字段变成字符串集合

```java
List<String> comboIds = personalComboInfos.stream()
    .map(PersonalComboInfo::getName)
    .collect(Collectors.toList());
```

#### 异常处理

**全局异常枚举类：**

```java
public enum ExceptionEnum implements BaseErrorInfoInterface {
    //自定义异常
    FAIL_QNUM(10001, "有答题人数不允许变动题目个数"),
    //有人做过公开团测，不允许修改
    FAIL_PUBLIC_BOUND(10002, "有人做过公开团测，不允许修改"),

    // 数据操作错误定义
    SUCCESS(200, "成功!"),
    BODY_NOT_MATCH(400, "请求的数据格式不符!"),
    SIGNATURE_NOT_MATCH(401, "请求的数字签名不匹配!"),
    NOT_FOUND(404, "未找到该资源!"),
    INTERNAL_SERVER_ERROR(500, "服务器内部错误!"),
    USER_GROUP_ALREADY_BOUND(501, "用户已经绑定团测!"),
    SERVER_BUSY(503, "服务器正忙，请稍后再试!");

    /**
     * 错误码
     */
    private final Integer resultCode;

    /**
     * 错误描述
     */
    private final String resultMsg;

    ExceptionEnum(Integer resultCode, String resultMsg) {
        this.resultCode = resultCode;
        this.resultMsg = resultMsg;
    }

    @Override
    public Integer getResultCode() {
        return resultCode;
    }

    @Override
    public String getResultMsg() {
        return resultMsg;
    }

}
```

**2.业务异常类**

```java
/**
 * 业务异常
 *
 * @author xunyun
 */
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public final class ServiceException extends RuntimeException {

    @Serial
    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    private Integer code;

    /**
     * 错误提示
     */
    private String message;

    /**
     * 错误明细，内部调试错误
     */
    private String detailMessage;

    public ServiceException(String message) {
        this.message = message;
    }

    public ServiceException(String message, Integer code) {
        this.message = message;
        this.code = code;
    }

    public String getDetailMessage() {
        return detailMessage;
    }

    @Override
    public String getMessage() {
        return message;
    }

    public Integer getCode() {
        return code;
    }

    public ServiceException setMessage(String message) {
        this.message = message;
        return this;
    }

    public ServiceException setDetailMessage(String detailMessage) {
        this.detailMessage = detailMessage;
        return this;
    }
}
```

**3.抛出全局异常 自定义错误码**

```java
if (havePerson > 0) {
    throw new ServiceException(ExceptionEnum.FAIL_QNUM.getResultMsg(), ExceptionEnum.FAIL_QNUM.getResultCode());
}
```

#### 相关

**1.oss流程：**

```
1.启动minio服务
2.配置数据库config
3.新增runner：SystemApplicationRunner---初始化 system 模块对应业务数据 

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://qiangrens-organization.gitbook.io/qkd90/kai-fa-kuang-jia-he-gong-ju/1.-hou-duan-guan-li-ping-tai-wen-dang.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
