Compare commits

...

133 Commits
main ... 11111

Author SHA1 Message Date
CrazyIter_Bin a70efb3c7b update
1 month ago
PL e8dfcf7f9f 聊天评语设置接口
1 month ago
zhouj1203@hotmail.com 523dfa47b6 处理年级与班级数据返回工具类
1 month ago
zhouj1203@hotmail.com 5200ec6f8c 更新名单接口,优化内容访问接口的方式,新增工具类操作外部接口
1 month ago
PL 678e7d302b 添加用于在Java对象和JSON数据之间进行快速、‌高效的转换。
1 month ago
PL d333e6809f Merge remote-tracking branch 'origin/develop' into develop
1 month ago
PL 812456a8e1 国培项目不能访问ai的问题,安装包的问题
1 month ago
PL 81a83a7566 将正式站数据库改为测试站数据库
1 month ago
zhouj1203@hotmail.com 6587bcca67 更新艺术评测相关内容 名单提取
1 month ago
PL d880be517b 处理配置问题
1 month ago
zhouj1203@hotmail.com f46d24223f Merge remote-tracking branch 'origin/develop' into develop
1 month ago
zhouj1203@hotmail.com 8e9d78dcc4 艺术评测相关内容
1 month ago
Li c20bbd2cf4 将科大讯飞的配置由原来的个人修改为企业配置,暂时注释个人配置信息
1 month ago
zhouj1203@hotmail.com 3451287df9 更新字段注解
1 month ago
zhouj1203@hotmail.com a1aa8c7187 新增班会科目逻辑代码,配置文件改为正式地址
1 month ago
zhouj1203@hotmail.com db6c7d402f 更新参数传递方式,调整参数解析内容,新增工具类,新增条件Dto
1 month ago
zhouj1203@hotmail.com a801724b75 redis 课堂纪律相关内容
1 month ago
winter c9e7268d87 fix: 修改 chatApp 权限时报
5 months ago
winter b7126f7bd8 refactor: 修改接口
5 months ago
winter 5d33226f94 refactor: ai接口token header修改
5 months ago
winter 5caf6de0ec refactor: ai接口token header修改
5 months ago
winter 2698f759a4 refactor: ai接口token header修改
5 months ago
winter f966dacb9c refactor: ai接口token header修改
5 months ago
winter a53efd1f7a fix: 修复 api
6 months ago
winter acf448e980 feat: 额外对国培AI对话接口的鉴权
6 months ago
winter 9aec66d1a7 refactor: 更改日志上传的api地址
6 months ago
winter b53fe0f7d6 feat: 新增接口日志上传拦截器
6 months ago
winter ea5c6fcb81 test: 暂时修改ai为public,下次更新回复先前版本
6 months ago
winter 52773b45ff refactor: 更新 pdf 模板
6 months ago
winter bbef6070fd refactor: 更新 pdf 模板
6 months ago
winter a00e0eaf2e refactor: 更新学生评价模板
6 months ago
winter 1a006ee304 fix: 添加字体文件,用代码让JChartFree用上simsun字体
7 months ago
winter 7aa14b1b82 fix: 修复乱码
7 months ago
winter c2c1f9cd0b fix: 测试线上Jfreechart图表汉字乱码
7 months ago
winter 32f2414cd8 fix: 饼图无数据时给与提示
7 months ago
winter fa1c6a9b20 refactor: 重构 PDF 模板
7 months ago
winter b9950c6022 refactor: 更新学生报告pdf
7 months ago
winter da1bb7fc87 feat: 值周巡检首页数据总览接口
7 months ago
winter 09140678bc feat: 新增首页数据展示
7 months ago
winter 9ff280a94e refactor: 拍照巡检暂时不用地点 id
7 months ago
winter 29f1fed4f1 feat: 新增通过 id 查询新闻
7 months ago
winter 94ac4286c3 fix: 修复计算学期学年的 start bug
7 months ago
winter 9f0cd7d01a refactor: 新闻新增学段字段
7 months ago
winter a539afb9e8 refactor: 新闻新增学段字段
7 months ago
winter 21790d5666 feat: 完善 News CRUD
7 months ago
winter 53f9492eb0 feat: 新闻的增删改查
7 months ago
winter 079f2fbf6f refactor: 更新 pdf 模板
7 months ago
winter bc13f67a29 feat: 学生评价的 pdf 生成
7 months ago
winter d5ad605b35 feat: 补充 chatApp 字段
8 months ago
winter 118fc413d3 fix: 修复sql小bug
8 months ago
winter 0ac22b5353 feat: 新增 pdfUtils 填充 stampter form
8 months ago
winter 8b25e112e8 feat: 学生评价报告 pdf 导出
8 months ago
winter f3e3f48422 feat: AI 应用初步实现
8 months ago
winter e74fee8812 refactor: 修改 tokenfilter 日志
8 months ago
winter 8bc0da460b refactor: 重构学生评价树的新增/更新节点,自构建 path
8 months ago
winter 70ffad27a3 feat: 新增后台条件查询值周评价
8 months ago
winter 2fb38aa62c refactor: 调整 admin dto 目录
8 months ago
winter 48588b12fc fix: 修复撤回值周评价
8 months ago
winter 256b233b4c refactor: 前台查询老师评价值周明细接口参数调整
8 months ago
winter 8c15427ff7 feat: 撤回值周投票
8 months ago
winter d155efe947 refactor: 钉钉异常信息推送异常栈的详情
9 months ago
winter 1a18105096 feat: 新增多条件查询评价明细接口
9 months ago
winter 327e399d5a refactor: 将接口文档名称标识"
9 months ago
winter 9f77aa9ede feat: 初步实现值周巡检树以及评价接口
9 months ago
winter 38699011e6 feat: 值周巡检投票
9 months ago
winter eec45f6921 feat: 新增值周巡检数个接口
9 months ago
winter 65c8fd598f refactor: 将包 dao 改为 repository
9 months ago
winter 5c01fca6c1 fix: 修复会话的 updateTime 为最后一条消息的发送时间
9 months ago
winter 0ded5af82d fix: 不是因为单例 是因为 Date 参数
9 months ago
winter ab2aaa1757 fix: 修复因为单例 OkHttpClient 的使用导致ws连接失败的bug
9 months ago
winter f2e0c0b19e fix: 修复跨域配置
9 months ago
winter fb49fea68e feat: 拉取聊天记录
9 months ago
winter d1bb7d56a9 feat: 拉取聊天记录
9 months ago
winter 56700192d9 fix: 修复学生排名没有头像
9 months ago
winter c9309087c3 doc: 增加一些代码注释
9 months ago
winter 8301c973c0 feat: 评价指标排序添加 path 和 praise
9 months ago
winter 178e3cfc03 doc: 补充 Readme
9 months ago
winter 83bdf4d6c1 fix: 学生排名增加学生头像
9 months ago
winter 2f972c65e3 fix: 修复成就计算的算法
9 months ago
winter ae729c05e8 feat: 获取评价树时root节点排序
9 months ago
winter 40d4826695 fix: 修复学生报告中的排位问题
9 months ago
winter ef1979cbd7 feat: 新增学生报告的指标
9 months ago
winter cec0a7e58d fix: 修复成就的 by zero 错误和增强参数校验
9 months ago
winter 6cf0ebaef3 doc: 补充部分文档
9 months ago
winter 316f9daa4a fix: 修复管理员首页数据接口如果多页数据结果异常的 bug
9 months ago
winter d185a3ffe7 refactor: 调整评价树的学段id,现摒弃default为默认,每个学段id都有自己的评价树
9 months ago
winter 646caaa926 refactor: 调整创建会话的接口,返回创建的会话ID
9 months ago
winter 76499c3904 fix: 修复查询会话的sql和updatetime
9 months ago
winter fede04c70d feat: 新增对话历史上下文缓存
9 months ago
winter fa03927579 refactor: 合并
9 months ago
winter 6ea7454973 feat: chat session 的实现
9 months ago
winter ccbd59f18f feat: chat session 的实现
9 months ago
winter fee64cb1ac feat: 初步走通 spark Gpt 的对接流程,留下扩展其他 AI 的空间
9 months ago
winter 0bae71207b fix: 修复评价记录按时间分页失效,改为按时间范围查询
9 months ago
winter aa35b01844 Merge remote-tracking branch 'origin/develop' into develop
9 months ago
winter 8ddf66c97d fix: 修改撤回学生评价权限的 bug
9 months ago
CrazyIter_Bin 890d23ae6a update
9 months ago
winter b610194f30 feat: 新增评价项 path 路径
9 months ago
winter 7918786641 feat: 新增被评价学生总数与补充评价对象的班级名
9 months ago
winter 79db32c215 feat: 新增部分管理员接口以及提升稳定性
9 months ago
winter 3477592027 feat: 下班!!!
10 months ago
winter 83c980dcae feat: 管理员相关接口
10 months ago
winter a138e16b40 feat: 首页数据展示(评价总数,按周统计等)
10 months ago
winter 6d631563c6 feat: vote 接口添加是否推送给家长字段
10 months ago
winter 8f7b1f9cc1 feat: 为查询学生评价记录添加分页和排序
10 months ago
winter c54edf3cfe fix: 暂时解决查询 Student 序列化错误的bug
10 months ago
winter a3922d8c2d feat: 新增撤回评价的接口
10 months ago
winter 6c47e0c59e refactor: 完善添加评价记录的逻辑(如果一个student确是不同的班级)
10 months ago
winter 18d42a36e6 doc: 完善已有的接口文档
10 months ago
winter a09d61b020 fix: 增加条件查询学生评价条件项为空白字符串时不计入条件的接口健壮性
10 months ago
winter c34e87ca0d feat: 新增评价树的模板替换与节点 ID 刷新
10 months ago
winter 18cfb50a46 fix: 添加 validation 依赖,修复参数校验不生效的 bug
10 months ago
winter a2d75a1c3d fix: 修复首次添加评价时不会记录得分的bug
10 months ago
winter 29d21f0233 新增评价学生或班级接口
10 months ago
winter 6dffd636ec feat: 新增以及完善部分数据结构
10 months ago
winter 8301cfd96d feat: 新增根据指定日期获取学年的工具
10 months ago
winter aa582361f8 refactor: 重构实体类,按其归属分类
10 months ago
winter 38f5bf39ef fix: 修复查询结果多就出错的bug
10 months ago
winter edc79455cc refactor: 抽取公共的实体类 BaseItem
10 months ago
winter 610c6d4e81 refactor: 将 evaluation 重构成 appraise
10 months ago
winter 5e06e8ed1e doc: 更新注释文档
10 months ago
winter d9a19736f2 feat: 不允许序列化为空的字段
10 months ago
winter 2b271547e7 feat: 新增鉴权时的异常处理
10 months ago
winter 148c31b323 fix: 从 token 中拿到 schoolId 而不是传递参数
10 months ago
winter 68f5303a4c 新增对评价树的crud以及测试bug修复
10 months ago
winter 1be6ca7e02 feat: 新增获取评价项树的接口
10 months ago
winter 46b88742d7 fix: 修复生成树的空指针bug
10 months ago
winter 676f6fc46b feat: 新增构建学生评价树
10 months ago
winter a16e8f8de8 doc: 补充文档
10 months ago
winter f0a9ba9243 feat: 新增钉钉机器人异常告警
10 months ago
winter 46fa7bf45e doc: 新增 README.md
10 months ago
winter c1e2a62527 feat: 新增双 token 鉴权实现
10 months ago
winter 38bb428c59 refactor: 项目改名为 Teammodel-extension
11 months ago

1
.gitignore vendored

@ -31,3 +31,4 @@ build/
### VS Code ###
.vscode/
/.vs

@ -0,0 +1,31 @@
# TeamModel extension
> SpringBoot base version of TeamModel extension
>
> **注意**: 所有复盘输出均已脱敏,不包含任何业务,密码等关键信息
## 迁移目录:
- Azure OIDC(SSO) 迁移
- id-token(jwt) 验证迁移 (出现语言框架之间的签名算法规范问题,解决见: [输出复盘](https://juejin.cn/post/7300036605099163702))
- 钉钉告警: 异常通知
- 异常文件记录
### MILESTONE:
- Java 框架搭建
- 教育评价系统前后台实现
- Chat with AI 接入
## 数据表规则:
> ID 没有特殊之指明则为 UUID
### 教育评价树
分区键: `Appraise` , 表内 `schoolId` 区分学校, `periodId` 区分学区
### 教育评价项
> 学生每学期所有的评价项都在一个项中,按学校进行分区
>
> 注意: 如果学生中途换班,也就是 classId 发生变动,那么会给这个学生在本学期新开一个文档,也就是说一个学生在一个学期的文档可能不止一个(小概率)
>
分区键: `AppraiseRecord-{学校id}`
academicYearId: `学年 + semesterId` -> eg: 2022.uuid

@ -9,32 +9,219 @@
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.teammodel</groupId>
<artifactId>deployment-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>deployment-test</name>
<description>deployment-test</description>
<artifactId>Teammodel-extension</artifactId>
<version>1.0.1</version>
<name>Teammodel-extension</name>
<description>Teammodel-extension</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud-azure.version>4.11.0</spring-cloud-azure.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.6.0</version>
</dependency>
<!-- Spring Security OAuth2 resource server -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!-- pdf操作类 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!--用于jfreechart制作统计图 -->
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- 选择一个具体的日志实现例如Logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<!-- cosmos -->
<!-- <dependency>-->
<!-- <groupId>com.azure.spring</groupId>-->
<!-- <artifactId>spring-cloud-azure-starter</artifactId>-->
<!-- </dependency>-->
<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-data-cosmos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 修改后的jjwt
<dependency>
<groupId>jsonwebtoken</groupId>
<artifactId>habook-jjwt</artifactId>
<version>0.0.1</version>
</dependency>
-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- dingding -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<!-- 指定依赖的作用范围 -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.48</version>
</dependency>
<!--用于在Java对象和JSON数据之间进行快速、高效的转换-->
</dependencies>
<!-- 私服 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-dependencies</artifactId>
<version>${spring-cloud-azure.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>gitea</id>
<url>http://163.228.224.105:3000/api/packages/winteach/maven</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitea</id>
<url>http://163.228.224.105:3000/api/packages/winteach/maven</url>
</repository>
<snapshotRepository>
<id>gitea</id>
<url>http://163.228.224.105:3000/api/packages/winteach/maven</url>
</snapshotRepository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- 请使用你项目中实际的版本号 <version>1.0.1</version> -->
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>

@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeploymentTestApplication {
public class TeamModelExtensionApplication {
public static void main(String[] args) {
SpringApplication.run(DeploymentTestApplication.class, args);
SpringApplication.run(TeamModelExtensionApplication.class, args);
}
}

@ -0,0 +1,118 @@
package cn.teammodel.ai;
import cn.hutool.json.JSONUtil;
import cn.teammodel.ai.cache.HistoryCache;
import cn.teammodel.ai.domain.SparkChatRequestParam;
import cn.teammodel.ai.listener.SparkGptStreamListener;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* spark Gpt client
* @author winter
* @create 2023-12-15 14:29
*/
@Component
@Data
@Slf4j
public class SparkGptClient implements InitializingBean {
@Resource
private SparkGptProperties sparkGptProperties;
private OkHttpClient okHttpClient;
// private String authUrl;
/**
*
*/
public void init() {
// 初始化缓存
HistoryCache.init(sparkGptProperties.getCache_timeout(), sparkGptProperties.getCache_context());
// 初始化 authUrl
// log.info("[SPARK CHAT] 鉴权 endpoint : {}", this.authUrl);
this.okHttpClient = new OkHttpClient.Builder()
.connectTimeout(180, TimeUnit.SECONDS)
.readTimeout(180, TimeUnit.SECONDS) // sse 接口的 readTimeout 不能设置小了
.writeTimeout(180, TimeUnit.SECONDS)
.build();
}
/**
* , sse
*/
public void streamChatCompletion(SparkChatRequestParam param, SparkGptStreamListener listener) {
try {
param.setAppId(sparkGptProperties.getAppId());
String authUrl = genAuthUrl(sparkGptProperties.getEndpoint(), sparkGptProperties.getApiKey(), sparkGptProperties.getApiSecret());
authUrl = authUrl.replace("http://", "ws://").replace("https://", "wss://");
Request request = new Request.Builder().url(authUrl).build();
// 设置请求参数
listener.setRequestJson(param.toJsonParams());
log.info("[SPARK CHAT] 请求参数 {}", JSONUtil.parseObj(param.toJsonParams()).toStringPretty());
// 不能使用单例 client
okHttpClient.newWebSocket(request, listener);
} catch (Exception e) {
log.error("[SPARK CHAT] Spark AI 请求异常: {}", e.getMessage());
e.printStackTrace();
}
}
/**
* URL
*/
public static String genAuthUrl(String endpoint, String apiKey, String apiSecret) {
URL url = null;
String date = null;
String preStr = null;
Mac mac = null;
try {
url = new URL(endpoint);
// 时间
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
date = format.format(new Date());
// 拼接
preStr = "host: " + url.getHost() + "\n" +
"date: " + date + "\n" +
"GET " + url.getPath() + " HTTP/1.1";
// SHA256加密
mac = Mac.getInstance("hmacsha256");
SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");
mac.init(spec);
} catch (Exception e) {
log.error("[SPARK CHAT] 生成鉴权URL失败, endpoint: {}, apiKey: {}, apiSecret: {}", endpoint, apiKey, apiSecret);
throw new RuntimeException(e);
}
byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
// Base64加密
String sha = Base64.getEncoder().encodeToString(hexDigits);
// 拼接
String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
// 拼接地址
HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().
addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).
addQueryParameter("date", date).//
addQueryParameter("host", url.getHost()).//
build();
return httpUrl.toString();
}
@Override
public void afterPropertiesSet() throws Exception {
init();
}
}

@ -0,0 +1,28 @@
package cn.teammodel.ai;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author winter
* @create 2023-12-15 14:29
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "spark.gpt")
public class SparkGptProperties {
private String endpoint;
private String appId;
private String apiKey;
private String apiSecret;
/**
*
*/
private Long cache_timeout;
/**
*
*/
private Integer cache_context;
}

@ -0,0 +1,30 @@
package cn.teammodel.ai;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@UtilityClass
@Slf4j
public class SseHelper {
/**
* server client sse
*/
public void complete(SseEmitter sseEmitter) {
try {
sseEmitter.complete();
} catch (Exception e) {
}
}
/**
* client
*/
public void send(SseEmitter sseEmitter, Object data) {
try {
sseEmitter.send(data);
} catch (Exception e) {
log.error("sseEmitter send error", e);
}
}
}

@ -0,0 +1,62 @@
package cn.teammodel.ai.cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.collection.ListUtil;
import cn.teammodel.model.entity.ai.ChatSession.Message;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import java.util.List;
/**
*
* @author winter
* @create 2023-12-20 11:02
*/
@Slf4j
@SuppressWarnings("unchecked")
public class HistoryCache {
private static TimedCache<Object, Object> HISTORY;
private static Integer contextSize = 3;
/**
*
*/
public static void init(Long timeout, Integer contextNum) {
contextSize = contextNum;
HISTORY = CacheUtil.newTimedCache(timeout);
// 一分钟清理一次
HISTORY.schedulePrune(60 * 1000);
}
public static List<Message> getContext(String sessionId) {
return (List<Message>) HISTORY.get(sessionId);
}
public static void putContext(String sessionId, List<Message> context) {
HISTORY.put(sessionId, context);
}
public static void removeContext(String sessionId) {
HISTORY.remove(sessionId);}
/**
* , contextSize
*/
public static void updateContext(String sessionId, Message message) {
List<Message> messages = (List<Message>)HISTORY.get(sessionId);
if (ObjectUtils.isEmpty(messages)) {
List<Message> context = ListUtil.of(message);
HISTORY.put(sessionId, context);
} else if (messages.size() >= contextSize) {
// 队列
messages.remove(0);
messages.add(message);
} else {
messages.add(message);
}
}
}

@ -0,0 +1,86 @@
package cn.teammodel.ai.domain;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Builder.Default;
import lombok.Data;
import java.util.List;
/**
* endpoint
* @author winter
* @create 2023-12-15 16:04
*/
@Data
@Builder
// 注意这两个注解一起使用会让无参构造丢失
public class SparkChatRequestParam {
//应用appid从开放平台控制台创建的应用中获取
private String appId;
//每个用户的id用于区分不同用户
private String uid;
//指定访问的领域,general指向V1.5版本 generalv2指向V2版本。注意不同的取值对应的url也不一样
@Default
private String domain = "generalv3";
//核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高
@Default
private Float temperature = 0.5F;
//模型回答的tokens的最大长度
@Default
private Integer maxTokens = 2048;
//从k个候选中随机选择⼀个⾮等概率
@Default
private Integer top_k = 4;
/**
* (sessionId)
*/
private String chatId;
private List<Message> messageList;
@Data
@AllArgsConstructor
public static class Message {
private String role;
private String content;
/**
* ,使,
*/
public static Message ofUser(String content){
return new Message("user",content);
}
public static Message ofAssistant(String content){
return new Message("assistant",content);
}
}
public String toJsonParams(){
ObjectMapper om = new ObjectMapper();
ObjectNode root = om.createObjectNode();
ObjectNode header = om.createObjectNode();
header.put("app_id",appId);
header.put("uid",uid);
ObjectNode parameter = om.createObjectNode();
ObjectNode chat = om.createObjectNode();
chat.put("domain", domain);
chat.put("temperature", temperature);
chat.put("max_tokens", maxTokens);
chat.put("top_k", top_k);
chat.put("chat_id", chatId);
parameter.set("chat", chat);
ObjectNode payload = om.createObjectNode();
payload.set("message", om.createObjectNode().putPOJO("text", messageList));
root.set("header", header);
root.set("parameter", parameter);
root.set("payload", payload);
return root.toString();
}
}

@ -0,0 +1,77 @@
package cn.teammodel.ai.domain;
import lombok.Data;
import java.util.List;
/**
* spark ws
* @Author: winter
*/
@Data
public class SparkChatResponse {
private Header header;
private Payload payload;
@Data
public static class Header{
/**
* 00
*/
private Integer code;
private String message;
private String sid;
/**
* [0,1,2]012
*/
private Integer status;
}
@Data
public static class Payload{
private Choices choices;
private Usage usage;
}
@Data
public static class Choices{
private Integer status;
private Integer seq;
private List<Text> text;
}
@Data
public static class Usage{
private UsageText text;
}
@Data
public static class UsageText{
private Integer question_tokens;
/**
* tokens
*/
private Integer prompt_tokens;
/**
* tokens
*/
private Integer completion_tokens;
/**
* prompt_tokenscompletion_tokenstokens
*/
private Integer total_tokens;
}
@Data
public static class Text{
/**
* AI
*/
private String content;
/**
* assistantAI
*/
private String role;
private Integer index;
}
}

@ -0,0 +1,95 @@
package cn.teammodel.ai.listener;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.teammodel.ai.SseHelper;
import cn.teammodel.ai.domain.SparkChatResponse;
import cn.teammodel.common.ErrorCode;
import cn.teammodel.config.exception.ServiceException;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.util.function.Consumer;
/**
* okhttp ws listener
* @author winter
* @create 2023-12-15 16:17
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
@RequiredArgsConstructor
public class SparkGptStreamListener extends WebSocketListener {
private final SseEmitter sseEmitter;
private String requestJson;
/**
*
*/
private String answer = "";
@Override
public void onOpen(WebSocket webSocket, @NotNull Response response) {
// ws 建立连接后发送请求的数据, 在 onMessage 中接受 server 的响应数据
webSocket.send(requestJson);
// 执行成功回调
try {
onOpen.accept(webSocket);
} catch (Exception e) {
this.onFailure(webSocket, e, response);
}
}
@Override
public void onMessage(WebSocket webSocket, String text) {
// 向 sse 推送消息(一次 onMessage 事件推送一次)
SparkChatResponse sparkChatResponse = JSONUtil.toBean(text, SparkChatResponse.class);
// 请求是否异常
if (sparkChatResponse.getHeader().getCode() != 0) {
this.onFailure(
webSocket,
new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(),
sparkChatResponse.getHeader().getMessage()),
null
);
}
// 推送回答 segment
String msgSegment = sparkChatResponse.getPayload().getChoices().getText().get(0).getContent();
// 处理消息格式(空格和换行符)
// msgSegment = StrUtil.replace(msgSegment, " ", "&#32;").replaceAll("\n", "&#92n");
msgSegment = StrUtil.replace(msgSegment, " ", "&#32;").replaceAll("\n", "<br>");
answer += msgSegment;
SseHelper.send(sseEmitter, msgSegment);
// 处理模型的最终响应
if (sparkChatResponse.getHeader().getStatus() == 2) {
// 其实 spark 会主动断开连接
webSocket.close(1000, "done");
onComplete.accept(answer);
SseHelper.complete(sseEmitter);
}
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, @Nullable Response response) {
webSocket.close(1000, t.getMessage());
this.onError.accept(t);
// 失败时结束 sse 连接
SseHelper.send(sseEmitter,t.getMessage() + "[DONE]");
SseHelper.complete(sseEmitter);
}
// 这几个 function 可以在 listener 被调用时设置, 实现类似事件的回调(可以使用模板方法模式实现)
protected Consumer<WebSocket> onOpen = (s) -> {};
protected Consumer<Throwable> onError = (s) -> {};
protected Consumer<String> onComplete = (s) -> {};
}

@ -0,0 +1,50 @@
package cn.teammodel.aop;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
@Aspect
@Component
@Slf4j
public class LogInterceptor {
/**
*
*/
@Around("execution(* cn.teammodel.controller.*.*(..))")
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
// 计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 获取请求路径
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
// 生成请求唯一 id
String requestId = UUID.randomUUID().toString();
String url = httpServletRequest.getRequestURI();
// 获取请求参数
Object[] args = point.getArgs();
String reqParam = "[" + StringUtils.join(args, ", ") + "]";
// 输出请求日志
log.info("request startid: {}, path: {}, ip: {}, params: {}", requestId, url,
httpServletRequest.getRemoteHost(), reqParam);
// 执行原方法
Object result = point.proceed();
// 输出响应日志
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
return result;
}
}

@ -0,0 +1,46 @@
package cn.teammodel.common;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author winter
* @create 2024-02-01 15:30
*/
public enum ChatAppScopeEnum {
PUBLIC("public", "公共"),
SCHOOL("school", "学校"),
PRIVATE("private", "私人");
private final String code;
private final String name;
public static List<String> codes() {
return Arrays.stream(values()).map(ChatAppScopeEnum::getCode).collect(Collectors.toList());
}
/**
* name code
*/
public static String getCodeByName(String name) {
ChatAppScopeEnum chatAppScopeEnum = Arrays.stream(values()).filter(item -> item.getName().equals(name)).findFirst().orElse(null);
String res = null;
if (chatAppScopeEnum != null) {
res = chatAppScopeEnum.getCode();
}
return res;
}
ChatAppScopeEnum(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
}

@ -0,0 +1,10 @@
package cn.teammodel.common;
/**
* @author winter
* @create 2023-11-29 14:26
*/
public interface CommonConstant {
String DASH = "-";
}

@ -0,0 +1,37 @@
package cn.teammodel.common;
public enum ErrorCode {
SUCCESS(200, "ok"),
PARAMS_ERROR(40000, "请求参数错误"),
NOT_LOGIN_ERROR(40100, "未登录"),
NO_AUTH_ERROR(40101, "无权限"),
NOT_FOUND_ERROR(40400, "请求数据不存在"),
FORBIDDEN_ERROR(40300, "禁止访问"),
SYSTEM_ERROR(50000, "系统内部异常"),
OPERATION_ERROR(50001, "操作失败");
/**
*
*/
private final int code;
/**
*
*/
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}

@ -0,0 +1,50 @@
package cn.teammodel.common;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* @author winter
* @create 2023-12-14 12:19
*/
public enum FiveEducations {
VIRTUE("virtue", "德育"),
INTELLIGENCE("intelligence","智育"),
SPORTS("sports", "体育"),
ART("art", "美育"),
LABOUR("labour", "劳育");
private final String code;
private final String name;
public static List<String> codes() {
return Arrays.stream(values()).map(FiveEducations::getCode).collect(Collectors.toList());
}
/**
* name code
*/
public static String getCodeByName(String name) {
FiveEducations fiveEducation = Arrays.stream(values()).filter(item -> item.getName().equals(name)).findFirst().orElse(null);
String res = null;
if (fiveEducation != null) {
res = fiveEducation.getCode();
}
return res;
}
FiveEducations(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
}

@ -0,0 +1,15 @@
package cn.teammodel.common;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author winter
* @create 2023-12-04 9:55
*/
@Data
public class IdRequest {
@NotBlank(message = "id 不能为空")
private String id;
}

@ -0,0 +1,48 @@
package cn.teammodel.common;
import com.azure.cosmos.models.PartitionKey;
/**
*
* @author winter
* @create 2023-11-22 15:31
*/
public interface PK {
/**
*
*/
String PK_APPRAISE = "Appraise";
/**
*
* : 1 , : SchoolId
*/
String PK_APPRAISE_RECORD = "AppraiseRecord-%s";
String COMMON_BASE = "Base";
/**
* , id
*/
String STUDENT = "Base-%s";
String CLASS = "Class-%s";
String CHAT_SESSION = "ChatSession";
String WEEK_DUTY = "Duty";
String WEEK_DUTY_RECORD = "DutyRecord-%s";
String CHAT_APP = "ChatApp";
String NEWS = "News-%s";
/**
*
*/
static PartitionKey of(String pk) {
return new PartitionKey(pk);
}
/**
* <br/>
* : ,
*/
static PartitionKey buildOf(String pk, String... args) {
String pkStr = String.format(pk, args);
return new PartitionKey(pkStr);
}
}

@ -0,0 +1,18 @@
package cn.teammodel.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
/**
* @author winter
* @create 2023-12-08 17:30
*/
@Data
@AllArgsConstructor
public class PageVo<T> {
private Integer totalPages;
private Long totalItems;
private List<T> content;
}

@ -0,0 +1,16 @@
package cn.teammodel.common;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author winter
* @create 2023-12-05 14:58
*/
@Data
public class PageableRequest {
@ApiModelProperty("当前页码, 默认: 0")
private Integer current = 0;
@ApiModelProperty("每页数量, 默认: 10")
private Integer size = 10;
}

@ -1,37 +1,48 @@
package cn.teammodel.common;
public class R {
private Integer code;
private String message;
private String data;
public R(Integer code, String message, String data) {
this.code = code;
this.message = message;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
package cn.teammodel.common;
import lombok.Data;
import java.io.Serializable;
@Data
public class R<T> implements Serializable {
private int code;
private T data;
private String message;
public R(int code, T data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
public R(int code, T data) {
this(code, data, "");
}
public R(ErrorCode errorCode) {
this(errorCode.getCode(), null, errorCode.getMessage());
}
public R(ErrorCode errorCode, T data) {
this(errorCode.getCode(), data, errorCode.getMessage());
}
public static <T> R<T> success(T data) {
return new R<>(ErrorCode.SUCCESS, data);
}
public static <T> R<T> error(String msg) {
return new R<>(ErrorCode.SYSTEM_ERROR.getCode(), null, msg);
}
public static <T> R<T> error(Integer code, String msg) {
return new R<>(code, null, msg);
}
public static <T> R<T> error(ErrorCode errorCode) {
return new R<>(errorCode);
}
}

@ -0,0 +1,55 @@
package cn.teammodel.config;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* request inputstream controller
*/
public class BodyReaderRequestWrapper extends HttpServletRequestWrapper {
// 将流中的内容保存
private final byte[] buff;
public BodyReaderRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 将 body 中的内容读取到 buff 中
InputStream is = request.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1) {
baos.write(b, 0, len);
}
buff = baos.toByteArray();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(buff);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return bais.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
public String getRequestBody() {
return new String(buff);
}
}

@ -0,0 +1,41 @@
package cn.teammodel.config;
import cn.teammodel.config.intercepter.EnhanceRequestServletFilter;
import cn.teammodel.config.intercepter.UploadApiLogInterceptor;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* WebMvc :
* @author winter
* @create 2024-01-15 17:06
*/
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final UploadApiLogInterceptor uploadApiLogInterceptor;
/**
*
*/
@Override
public void addInterceptors(@NotNull InterceptorRegistry registry) {
registry.addInterceptor(uploadApiLogInterceptor);
}
// 注册过滤器
@Bean
public FilterRegistrationBean<EnhanceRequestServletFilter> filterRegistrationBean(){
FilterRegistrationBean<EnhanceRequestServletFilter> filterRegistrationBean=new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new EnhanceRequestServletFilter());
filterRegistrationBean.addUrlPatterns("/*");
//order的数值越小 则优先级越高,这里直接使用的最高优先级
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegistrationBean;
}
}

@ -0,0 +1,126 @@
package cn.teammodel.config.exception;
import cn.teammodel.common.ErrorCode;
import cn.teammodel.common.R;
import cn.teammodel.manager.notification.NotificationService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
*
*
* @author ruoyi
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@Resource
private NotificationService notificationService;
/**
*
*/
@ExceptionHandler(ServiceException.class)
public R<?> handleServiceException(ServiceException e, HttpServletRequest request)
{
log.error(e.getMessage(), e);
Integer code = e.getCode();
return ObjectUtils.isNotEmpty(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public R<?> handleHttpMsgNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request)
{
log.error(e.getMessage(), e);
return R.error(ErrorCode.PARAMS_ERROR.getCode(), "参数格式有误");
}
/**
*
*/
@ExceptionHandler(MissingPathVariableException.class)
public R<?> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
return R.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
}
/**
*
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
return R.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
}
/**
*
*/
@ExceptionHandler(RuntimeException.class)
public R<?> handleRuntimeException(RuntimeException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
notificationService.send("RuntimeException 告警: " + getCausesAsString(e, 5));
return R.error(ErrorCode.SYSTEM_ERROR);
}
/**
*
*/
@ExceptionHandler(Exception.class)
public R<?> handleException(Exception e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
notificationService.send("Exception 告警: " + getCausesAsString(e, 5));
return R.error(e.getMessage());
}
/**
*
*/
@ExceptionHandler(BindException.class)
public R<?> handleBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return R.error(message);
}
/**
*
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
log.error(e.getMessage(), e);
String message = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
return R.error(message);
}
private static String getCausesAsString(Throwable throwable, int maxDepth) {
return Stream.iterate(throwable, Throwable::getCause)
.limit(maxDepth)
.map(e -> e.getClass().getSimpleName() + ": " + e.getMessage())
.collect(Collectors.joining(" <- "));
}
}

@ -0,0 +1,76 @@
package cn.teammodel.config.exception;
import cn.teammodel.common.ErrorCode;
/**
*
*
* @author winter
*/
public final class ServiceException extends RuntimeException
{
private static final long serialVersionUID = 1L;
/**
*
*/
private Integer code;
/**
*
*/
private String message;
/**
*
*/
private String detailMessage;
/**
*
*/
public ServiceException() {}
public ServiceException(String message)
{
this.code = ErrorCode.SYSTEM_ERROR.getCode();
this.message = message;
}
public ServiceException(Integer code, String message) {
this.message = message;
this.code = code;
}
public ServiceException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
}
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;
}
}

@ -0,0 +1,13 @@
package cn.teammodel.config.ies;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Getter
@Configuration
public class IESConfig {
@Value("${ies.server-url}")
private String serverUrl;
}

@ -0,0 +1,19 @@
package cn.teammodel.config.intercepter;
import cn.teammodel.config.BodyReaderRequestWrapper;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class EnhanceRequestServletFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 防止流读取一次后就没有了, 所以需要将流继续写出去
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// 这里将原始request传入读出流并存储
ServletRequest requestWrapper = new BodyReaderRequestWrapper(httpServletRequest);
// 这里将原始request替换为包装后的request此后所有进入controller的request均为包装后的
filterChain.doFilter(requestWrapper, servletResponse);//
}
}

@ -0,0 +1,144 @@
package cn.teammodel.config.intercepter;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.jwt.JWTUtil;
import cn.teammodel.manager.notification.NotificationService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.List;
/**
*
* @author winter
* @create 2024-03-18 10:00
*/
@Component
@Slf4j
public class UploadApiLogInterceptor implements HandlerInterceptor {
@Value("${spring.env}")
private String env;
@Resource
private NotificationService notificationService;
private final ObjectMapper mapper = new ObjectMapper();
@Override
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
if (!env.equals("dev")) {
doUploadLog(request);
}
return true;
}
private void doUploadLog(@NotNull HttpServletRequest request) throws IOException {
String ip = ServletUtil.getClientIP(request);
String client = "";
String tokenSha = "";
String id = "";
String name = "";
String school = "";
String scope = "";
String referer = request.getHeader("Referer");
Long requestTime = Instant.now().toEpochMilli();
// 获取 json 请求参数(这里读了后,后面就再也读不到了,需要处理)
BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null)
responseStrBuilder.append(inputStr);
JsonNode jsonNode = mapper.readTree(responseStrBuilder.toString());
String xAuthToken = request.getHeader("x-auth-AuthToken");
// 如果有 authorization
String authorization = request.getHeader("Authorization");
if (StringUtils.isNotBlank(authorization)) {
authorization = authorization.replace("Bearer ", "");
List<String> roles = (List<String>) JWTUtil.parseToken(authorization).getPayload("roles");
if (ObjectUtils.isNotEmpty(roles)) {
client = roles.get(0);
}
tokenSha = SecureUtil.sha1("Bearer " + authorization);
}
String xAuthIdToken = request.getHeader("X-Auth-IdToken");
if (StringUtils.isNotBlank(xAuthIdToken)) {
id = (String) JWTUtil.parseToken(xAuthIdToken).getPayload("sub");
name = (String) JWTUtil.parseToken(xAuthIdToken).getPayload("name");
if (StringUtils.isNotBlank(tokenSha)) {
tokenSha = SecureUtil.sha1(xAuthIdToken);
}
}
String xAuthSchool = request.getHeader("X-Auth-School");
if (StringUtils.isNotBlank(xAuthSchool)) {
school = xAuthSchool;
}
if (StringUtils.isNotBlank(xAuthToken)) {
id = (String) JWTUtil.parseToken(xAuthToken).getPayload("sub");
name = (String) JWTUtil.parseToken(xAuthToken).getPayload("name");
school = (String) JWTUtil.parseToken(xAuthToken).getPayload("azp");
scope = (String) JWTUtil.parseToken(xAuthToken).getPayload("scope");
if (StringUtils.isNotBlank(tokenSha)) {
tokenSha = SecureUtil.sha1(xAuthToken);
}
}
ObjectNode root = mapper.createObjectNode();
root.put("ip", ip);
root.put("time", requestTime);
root.put("id", id);
root.put("name", name);
root.set("param", jsonNode);
root.put("school", school);
root.put("client", client);
root.put("tid", tokenSha);
root.put("scope", scope);
root.put("path", request.getContextPath() + request.getRequestURI());
root.put("host", request.getServerName());
root.put("p", "appraisal");
// 发送请求
OkHttpClient okHttpClient = new OkHttpClient();
String requestData = root.toString();
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), requestData);
Request okRequest = new Request.Builder()
.url("http://cdhabook.teammodel.cn:8805/api/http-log")
.post(requestBody)
.build();
okHttpClient.newCall(okRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
log.error("UploadApiLogIntercepter error") ;
notificationService.send("日志上传告警: 请求发送失败,检查请求发送客户端");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
log.error("UploadApiLogIntercepter error" ) ;
notificationService.send("日志上传告警: 请求响应异常,检查 API 源是否正常");
} else {
log.info("UploadApiLogIntercepter success");
}
}
});
}
}

@ -0,0 +1,35 @@
package cn.teammodel.config.knife;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* Knife4j <br/>
* https://doc.xiaominfo.com/knife4j/documentation/get_start.html
*/
@Configuration
@EnableSwagger2
//@Profile({"dev", "test"})
public class Knife4jConfig {
@Bean
public Docket defaultApi2() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("五育评价接口文档")
.description("五育评价接口描述")
.version("1.0.1")
.build())
.select()
// 指定 Controller 扫描包路径
.apis(RequestHandlerSelectors.basePackage("cn.teammodel.controller"))
.paths(PathSelectors.any())
.build();
}
}

@ -0,0 +1,34 @@
package cn.teammodel.config.redis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory dbConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("52.130.252.100", 6379);
config.setDatabase(8); // 设置数据库编号为8
config.setPassword("habook");
return new LettuceConnectionFactory(config);
}
@Bean
public StringRedisTemplate db1Template(LettuceConnectionFactory dbConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(dbConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}

@ -1,16 +0,0 @@
package cn.teammodel.controller;
import cn.teammodel.common.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
public class HelloController {
@GetMapping("hello")
public R helo() {
return new R(200, "sucess","hello world");
}
}

@ -0,0 +1,39 @@
package cn.teammodel.controller.admin.controller;
import cn.teammodel.common.R;
import cn.teammodel.controller.admin.service.AdminAppraiseService;
import cn.teammodel.model.dto.admin.appraise.UpdateAchievementRuleDto;
import cn.teammodel.model.entity.appraise.AchievementRule;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* @author winter
* @create 2023-12-13 16:07
*/
@RestController
@RequestMapping("admin/appraise")
@Api(tags = "管理员端-学生评价管理")
public class AdminAppraiseController {
@Resource
private AdminAppraiseService adminAppraiseService;
@PostMapping("updateAchieveRule")
@ApiOperation("更新的 rule 节点将会直接覆盖老节点")
public R<List<AchievementRule>> updateAchieveRule(@RequestBody @Valid UpdateAchievementRuleDto ruleDto) {
List<AchievementRule> res = adminAppraiseService.updateAchieveRule(ruleDto);
return R.success(res);
}
@GetMapping("getAchieveRules/{periodId}")
@ApiOperation("获取当前学段下的成就规则")
public R<List<AchievementRule>> getAchieveRules(@PathVariable String periodId){
List<AchievementRule> res = adminAppraiseService.getAchieveRules(periodId);
return R.success(res);
}
}

@ -0,0 +1,80 @@
package cn.teammodel.controller.admin.controller;
import cn.teammodel.common.R;
import cn.teammodel.controller.admin.service.AdminIndexDutyService;
import cn.teammodel.model.dto.admin.appraise.TimeRangeDto;
import cn.teammodel.model.dto.admin.weekduty.AdminFindDutyRecordDto;
import cn.teammodel.model.dto.weekDuty.LessonRecordDto;
import cn.teammodel.model.vo.admin.AppraiseNodeRankVo;
import cn.teammodel.model.vo.admin.DutyIndexData;
import cn.teammodel.model.vo.admin.DutyNodeRankVo;
import cn.teammodel.model.vo.admin.DutyRankPo;
import cn.teammodel.model.vo.weekDuty.DutyRecordVo;
import cn.teammodel.service.DutyService;
import com.google.gson.JsonElement;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
/**
* @author winter
* @create 2024-01-09 17:59
*/
@RestController
@RequestMapping("admin/duty")
@Api(tags = "管理员端-值周巡检")
public class AdminDutyController {
@Resource
private DutyService dutyService;
@Resource
private AdminIndexDutyService adminIndexDutyService;
@PostMapping("records")
@ApiOperation("获取班级评价数据")
public R<List<DutyRecordVo>> findRecords(@Valid @RequestBody AdminFindDutyRecordDto adminFindDutyRecordDto) {
List<DutyRecordVo> res = dutyService.findAdminRecords(adminFindDutyRecordDto);
return R.success(res);
}
@GetMapping("index/{periodId}")
@ApiOperation("获取首页数据")
public R<DutyIndexData> index(@PathVariable String periodId){
DutyIndexData dutyIndexData = adminIndexDutyService.getIndexData(periodId);
return R.success(dutyIndexData);
}
@PostMapping("classRank")
@ApiOperation("班级评价活跃排行榜: Top10")
public R<List<DutyRankPo>> classRank(@Valid @RequestBody TimeRangeDto timeRangeDto) {
List<DutyRankPo> res = adminIndexDutyService.classRank(timeRangeDto);
return R.success(res);
}
@PostMapping("teacherRank")
@ApiOperation("老师评价活跃排行榜: Top10")
public R<List<DutyRankPo>> teacherRank(@Valid @RequestBody TimeRangeDto timeRangeDto) {
List<DutyRankPo> res = adminIndexDutyService.teacherRank(timeRangeDto);
return R.success(res);
}
@PostMapping("dutyNodeRank")
@ApiOperation("评价指标活跃排行榜: Top10")
public R<List<DutyNodeRankVo>> appraiseNodeRank(@Valid @RequestBody TimeRangeDto timeRangeDto) {
List<DutyNodeRankVo> res = adminIndexDutyService.appraiseNodeRank(timeRangeDto);
return R.success(res);
}
@PostMapping("getLessonRecord")
@ApiOperation("获取课堂记录")
public R<Map<String, Object> > appraiseNodeRank(@Valid @RequestBody LessonRecordDto lessonRecordDto , HttpServletRequest request) {
Map<String, Object> res = adminIndexDutyService.getLessonRecord(lessonRecordDto,request);
return R.success(res);
}
}

@ -0,0 +1,39 @@
package cn.teammodel.controller.admin.controller;
import cn.teammodel.common.R;
import cn.teammodel.controller.admin.service.ArtService;
import cn.teammodel.model.dto.admin.art.ArtFindDto;
import cn.teammodel.model.dto.admin.common.GroupDto;
import cn.teammodel.model.dto.admin.common.RGroupList;
import cn.teammodel.model.vo.admin.ArtElementsVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("admin/art")
@Api(tags = "管理员端-艺术评测")
public class ArtController {
@Resource
private ArtService ArtService;
@PostMapping("getArtList")
@ApiOperation("获取当前学校艺术评测列表")
public R<List<ArtElementsVo>> findRecords(@Valid @RequestBody ArtFindDto artFindDto,HttpServletRequest request) {
List<ArtElementsVo> res = ArtService.getArtList(artFindDto,request);
return R.success(res);
}
@PostMapping("getGroupList")
@ApiOperation("获取当前学校指定成员名单详细信息")
public R<List<RGroupList>> findGroups(@Valid @RequestBody GroupDto groupDto, HttpServletRequest request) {
List<RGroupList> res = ArtService.getGroupList(groupDto,request);
return R.success(res);
}
}

@ -0,0 +1,35 @@
package cn.teammodel.controller.admin.controller;
import cn.teammodel.common.R;
import cn.teammodel.controller.admin.service.ArtService;
import cn.teammodel.controller.admin.service.CommonService;
import cn.teammodel.model.dto.admin.art.ArtFindDto;
import cn.teammodel.model.dto.admin.common.GCDto;
import cn.teammodel.model.vo.admin.ArtElementsVo;
import cn.teammodel.model.vo.admin.GradeAndClassVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("admin/common")
@Api(tags = "管理员端-公共组件")
public class CommonController {
@Resource
private CommonService commonService ;
@PostMapping("getGradeAndClass")
@ApiOperation("获取当前学校当前学段年级班级信息")
public R<List<GradeAndClassVo>> findRecords(@Valid @RequestBody GCDto gcDto) {
List<GradeAndClassVo> res = commonService.getGradeAndClass(gcDto);
return R.success(res);
}
}

@ -0,0 +1,68 @@
package cn.teammodel.controller.admin.controller;
import cn.teammodel.common.R;
import cn.teammodel.controller.admin.service.AdminAppraiseService;
import cn.teammodel.model.dto.admin.appraise.TimeRangeDto;
import cn.teammodel.model.vo.admin.*;
import cn.teammodel.model.vo.appraise.RecordVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
*
* @author winter
* @create 2023-12-06 14:37
*/
@RestController
@RequestMapping("admin/index")
@Api(tags = "管理员端-首页数据")
public class IndexController {
@Resource
private AdminAppraiseService adminAppraiseService;
@GetMapping("/{periodId}")
@ApiOperation("获取首页数据")
public R<AppraiseIndexData> index(@PathVariable String periodId){
AppraiseIndexData appraiseIndexData = adminAppraiseService.getIndexData(periodId);
return R.success(appraiseIndexData);
}
@PostMapping("latestRecord")
@ApiOperation("查询时间范围最近评价(无参则当前周的所有记录)")
public R<List<RecordVo>> latestRecord(@Valid @RequestBody TimeRangeDto timeRangeDto) {
List<RecordVo> res = adminAppraiseService.conditionLatestRecord(timeRangeDto);
return R.success(res);
}
@PostMapping("classRank")
@ApiOperation("班级评价活跃排行榜: Top10")
public R<List<RankPo>> classRank(@Valid @RequestBody TimeRangeDto timeRangeDto) {
List<RankPo> res = adminAppraiseService.classRank(timeRangeDto);
return R.success(res);
}
@PostMapping("studentRank")
@ApiOperation("学生评价活跃排行榜: Top10")
public R<List<StudentRankVo>> studentRank(@Valid @RequestBody TimeRangeDto timeRangeDto) {
List<StudentRankVo> res = adminAppraiseService.studentRank(timeRangeDto);
return R.success(res);
}
@PostMapping("teacherRank")
@ApiOperation("老师评价活跃排行榜: Top10")
public R<List<RankVo>> teacherRank(@Valid @RequestBody TimeRangeDto timeRangeDto) {
List<RankVo> res = adminAppraiseService.teacherRank(timeRangeDto);
return R.success(res);
}
@PostMapping("appraiseNodeRank")
@ApiOperation("评价指标活跃排行榜: Top10")
public R<List<AppraiseNodeRankVo>> appraiseNodeRank(@Valid @RequestBody TimeRangeDto timeRangeDto) {
List<AppraiseNodeRankVo> res = adminAppraiseService.appraiseNodeRank(timeRangeDto);
return R.success(res);
}
}

@ -0,0 +1,26 @@
package cn.teammodel.controller.admin.controller;
import cn.teammodel.common.R;
import cn.teammodel.test.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Map;
@RestController
@RequestMapping("/test")
public class RedisController {
private final RedisService redisService;
@Autowired
public RedisController(RedisService redisService) {
this.redisService = redisService;
}
@PostMapping("/redis")
public Map<Object, Object> getValueByKey(@RequestBody @Valid String key) {
Map<Object,Object> juri = redisService.getValueByKey(key);
return R.success(juri).getData();
}
}

@ -0,0 +1,34 @@
package cn.teammodel.controller.admin.service;
import cn.teammodel.model.dto.admin.appraise.TimeRangeDto;
import cn.teammodel.model.dto.admin.appraise.UpdateAchievementRuleDto;
import cn.teammodel.model.entity.appraise.AchievementRule;
import cn.teammodel.model.vo.admin.*;
import cn.teammodel.model.vo.appraise.RecordVo;
import java.util.List;
/**
* @author winter
* @create 2023-12-06 14:45
*/
public interface AdminAppraiseService {
AppraiseIndexData getIndexData(String periodId);
/**
*
*/
List<RecordVo> conditionLatestRecord(TimeRangeDto timeRangeDto);
List<RankPo> classRank(TimeRangeDto timeRangeDto);
List<RankVo> teacherRank(TimeRangeDto timeRangeDto);
List<AppraiseNodeRankVo> appraiseNodeRank(TimeRangeDto timeRangeDto);
List<StudentRankVo> studentRank(TimeRangeDto timeRangeDto);
List<AchievementRule> updateAchieveRule(UpdateAchievementRuleDto ruleDto);
List<AchievementRule> getAchieveRules(String periodId);
}

@ -0,0 +1,29 @@
package cn.teammodel.controller.admin.service;
import cn.teammodel.model.dto.admin.appraise.TimeRangeDto;
import cn.teammodel.model.dto.weekDuty.LessonRecordDto;
import cn.teammodel.model.vo.admin.DutyIndexData;
import cn.teammodel.model.vo.admin.DutyNodeRankVo;
import cn.teammodel.model.vo.admin.DutyRankPo;
import com.google.gson.JsonElement;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
/**
* @author winter
* @create 2024-02-28 15:07
*/
public interface AdminIndexDutyService {
DutyIndexData getIndexData(String periodId);
List<DutyRankPo> classRank(TimeRangeDto timeRangeDto);
List<DutyRankPo> teacherRank(TimeRangeDto timeRangeDto);
List<DutyNodeRankVo> appraiseNodeRank(TimeRangeDto timeRangeDto);
Map<String, Object> getLessonRecord (LessonRecordDto lessonRecordDto, HttpServletRequest request);
}

@ -0,0 +1,14 @@
package cn.teammodel.controller.admin.service;
import cn.teammodel.model.dto.admin.art.ArtFindDto;
import cn.teammodel.model.dto.admin.common.GroupDto;
import cn.teammodel.model.dto.admin.common.RGroupList;
import cn.teammodel.model.vo.admin.ArtElementsVo;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
public interface ArtService {
List<ArtElementsVo> getArtList(ArtFindDto artFindDto, HttpServletRequest request);
List<RGroupList> getGroupList(GroupDto groupDto, HttpServletRequest request);
}

@ -0,0 +1,10 @@
package cn.teammodel.controller.admin.service;
import cn.teammodel.model.dto.admin.common.GCDto;
import cn.teammodel.model.vo.admin.GradeAndClassVo;
import org.springframework.stereotype.Service;
import java.util.List;
public interface CommonService {
List<GradeAndClassVo> getGradeAndClass(GCDto gcDto);
}

@ -0,0 +1,403 @@
package cn.teammodel.controller.admin.service.impl;
import cn.teammodel.common.ErrorCode;
import cn.teammodel.common.PK;
import cn.teammodel.config.exception.ServiceException;
import cn.teammodel.controller.admin.service.AdminAppraiseService;
import cn.teammodel.repository.*;
import cn.teammodel.model.dto.admin.appraise.TimeRangeDto;
import cn.teammodel.model.dto.admin.appraise.UpdateAchievementRuleDto;
import cn.teammodel.model.entity.User;
import cn.teammodel.model.entity.appraise.AchievementRule;
import cn.teammodel.model.entity.appraise.Appraise;
import cn.teammodel.model.entity.appraise.AppraiseTreeNode;
import cn.teammodel.model.entity.school.ClassInfo;
import cn.teammodel.model.entity.school.School;
import cn.teammodel.model.entity.school.Student;
import cn.teammodel.model.entity.school.Teacher;
import cn.teammodel.model.vo.admin.*;
import cn.teammodel.model.vo.appraise.RecordVo;
import cn.teammodel.security.utils.SecurityUtil;
import cn.teammodel.utils.RepositoryUtil;
import cn.teammodel.utils.SchoolDateUtil;
import com.azure.cosmos.models.CosmosPatchOperations;
import com.azure.cosmos.models.PartitionKey;
import com.azure.spring.data.cosmos.core.query.CosmosPageRequest;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;
import static cn.teammodel.utils.SchoolDateUtil.calculateWeekNum;
/**
* @author winter
* @create 2023-12-06 14:46
*/
@Service
public class AdminAppraiseServiceImpl implements AdminAppraiseService {
@Resource
private SchoolRepository schoolRepository;
@Resource
private ClassRepository classRepository;
@Resource
private TeacherRepository teacherRepository;
@Resource
private AppraiseRepository appraiseRepository;
@Resource
private StudentRepository studentRepository;
@Resource
private AppraiseRecordRepository appraiseRecordRepository;
@Override
public AppraiseIndexData getIndexData(String periodId) {
final int SLICE_SIZE = 100;
if (StringUtils.isBlank(periodId)) {
throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "不能为空");
}
final AppraiseIndexData appraiseIndexData = new AppraiseIndexData();
int totalCount = 0;
int criticalCount = 0;
Set<String> creatorIdSet = new HashSet<>();
Set<String> studentSet = new HashSet<>();
User loginUser = SecurityUtil.getLoginUser();
String schoolId = loginUser.getSchoolId();
// 获取学期起止时间
List<School.Semester> semesters = schoolRepository.findSemestersById(schoolId, periodId);
SchoolDateUtil.semesterModel semesterModel = SchoolDateUtil.getSemesterByNow(semesters, LocalDate.now());
String academicYearId = semesterModel.getAcademicYearId();
LocalDateTime startDatetime = semesterModel.getStartDatetime();
LocalDateTime endDatetime = semesterModel.getEndDatetime();
if (startDatetime == null || endDatetime == null) throw new ServiceException(ErrorCode.PARAMS_ERROR);
long totalWeek = calculateWeekNum(startDatetime, endDatetime, null);
// slice 分段读取
CosmosPageRequest pageRequest = new CosmosPageRequest(0, SLICE_SIZE, null);
Slice<RecordVo> slice;
Map<Long, Integer> countByWeek = SchoolDateUtil.createEmptyWeekMap(totalWeek);
do {
slice = appraiseRecordRepository.findAllByAcademicYearId(String.format(PK.PK_APPRAISE_RECORD, schoolId), academicYearId, pageRequest);
List<RecordVo> content = slice.getContent();
if (ObjectUtils.isEmpty(content)) {
break;
}
// 分批次计算
for (RecordVo item : content) {
// 处理每周的评价数
long weekNum = calculateWeekNum(startDatetime, endDatetime, item.getCreateTime());
countByWeek.put(weekNum, countByWeek.getOrDefault(weekNum, 0) + 1);
// 处理总评价数
totalCount++;
// 处理批评数
if (!item.isPraise()) {
criticalCount++;
}
// 处理已评价老师总数
creatorIdSet.add(item.getCreatorId());
// 处理被评价的学生数
if ("student".equals(item.getTargetType())) {
studentSet.add(item.getTargetId());
}
}
if (slice.hasNext()) {
pageRequest = (CosmosPageRequest) slice.nextPageable();
}
} while (slice.hasNext());
// 组装数据
appraiseIndexData.setCountByWeek(countByWeek);
appraiseIndexData.setTotalCount(totalCount);
appraiseIndexData.setCriticalCount(criticalCount);
appraiseIndexData.setPraiseCount(totalCount - criticalCount);
appraiseIndexData.setTeacherCount(creatorIdSet.size());
appraiseIndexData.setStudentCount(studentSet.size());
return appraiseIndexData;
}
@Override
public List<RecordVo> conditionLatestRecord(TimeRangeDto timeRangeDto) {
Long startTime = timeRangeDto.getStartTime();
Long endTime = timeRangeDto.getEndTime();
String academicYearId = timeRangeDto.getAcademicYearId();
String schoolId = SecurityUtil.getLoginUser().getSchoolId();
// fixme: 是否对时间范围做一些限制(不能确保当前周有数据)
// 无参默认当前周
if (startTime == null || endTime == null) {
// 将时间范围调整为当前周的周一到当前时间
LocalDateTime mondayOfCurWeek = LocalDateTime.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
.withHour(0)
.withMinute(0)
.withSecond(0)
.withNano(0);
startTime = mondayOfCurWeek.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
endTime = Instant.now().toEpochMilli();
}
List<RecordVo> res = appraiseRecordRepository.latestRecords(
String.format(PK.PK_APPRAISE_RECORD, schoolId),
academicYearId,
startTime,
endTime
);
if (res != null) {
res = res.stream().sorted((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime())).collect(Collectors.toList());
}
return res;
}
@Override
public List<RankPo> classRank(TimeRangeDto timeRangeDto) {
// todo 不要 page| 批评和表扬数量
Long startTime = timeRangeDto.getStartTime();
Long endTime = timeRangeDto.getEndTime();
String academicYearId = timeRangeDto.getAcademicYearId();
String schoolId = SecurityUtil.getLoginUser().getSchoolId();
List<RankPo> rankPoList = appraiseRecordRepository.classRank(
String.format(PK.PK_APPRAISE_RECORD, schoolId),
academicYearId,
startTime,
endTime
);
if (ObjectUtils.isEmpty(rankPoList)) return null;
Set<String> classIdSet = rankPoList.stream().map(RankPo::getId).collect(Collectors.toSet());
// 注意: 如果查询 in 的查询集在数据库中不存在,则在结果集也不会为 null.
if (ObjectUtils.isEmpty(classIdSet)) return rankPoList;
List<ClassInfo> classes = classRepository.findAllByCodeAndIdIn(String.format(PK.CLASS, schoolId), classIdSet);
Map<String, String> idNameMap = classes.stream().collect(Collectors.toMap(ClassInfo::getId, ClassInfo::getName));
rankPoList.forEach(rankVo -> rankVo.setName(idNameMap.get(rankVo.getId())));
return rankPoList;
}
@Override
public List<RankVo> teacherRank(TimeRangeDto timeRangeDto) {
Long startTime = timeRangeDto.getStartTime();
Long endTime = timeRangeDto.getEndTime();
String academicYearId = timeRangeDto.getAcademicYearId();
String schoolId = SecurityUtil.getLoginUser().getSchoolId();
List<RankPo> rankPoList = appraiseRecordRepository.teacherRank(
String.format(PK.PK_APPRAISE_RECORD, schoolId),
academicYearId,
startTime,
endTime
);
// 根据 id 分组
Map<String, List<RankPo>> idRankPoMap = rankPoList.stream().collect(Collectors.groupingBy(RankPo::getId));
final List<RankVo> rankVos = new ArrayList<>();
idRankPoMap.forEach((id, list) -> {
RankVo rankVo = new RankVo();
int praiseCount = 0;
int criticalCount = 0;
rankVo.setId(id);
for (RankPo po : list) {
if (po.getIsPraise()) {
praiseCount += po.getCount();
} else {
criticalCount += po.getCount();
}
}
rankVo.setPraiseCount(praiseCount);
rankVo.setCriticalCount(criticalCount);
rankVo.setTotalCount(praiseCount + criticalCount);
rankVos.add(rankVo);
});
// 排序
List<RankVo> res = rankVos.stream()
.sorted(Comparator.comparing(RankVo::getTotalCount).reversed())
.collect(Collectors.toList());
// 设置 name
if (ObjectUtils.isEmpty(res)) return null;
Set<String> teacherIdSet = res.stream().map(RankVo::getId).collect(Collectors.toSet());
if (ObjectUtils.isEmpty(teacherIdSet)) return res;
List<Teacher> teachers = teacherRepository.findAllByCodeAndIdIn(PK.COMMON_BASE, teacherIdSet);
Map<String, String> idNameMap = teachers.stream().collect(Collectors.toMap(Teacher::getId, Teacher::getName));
res.forEach(rankVo -> rankVo.setName(idNameMap.get(rankVo.getId())));
return res;
}
@Override
public List<AppraiseNodeRankVo> appraiseNodeRank(TimeRangeDto timeRangeDto) {
Long startTime = timeRangeDto.getStartTime();
Long endTime = timeRangeDto.getEndTime();
String academicYearId = timeRangeDto.getAcademicYearId();
String schoolId = SecurityUtil.getLoginUser().getSchoolId();
List<AppraiseNodeRankVo> rankVoList = appraiseRecordRepository.appraiseNodeRank(
String.format(PK.PK_APPRAISE_RECORD, schoolId),
academicYearId,
startTime,
endTime
);
if (rankVoList == null) return null;
List<String> names = rankVoList.stream().map(AppraiseNodeRankVo::getName).collect(Collectors.toList());
if (ObjectUtils.isEmpty(names)) return null;
// 去重后的 nodes
List<AppraiseTreeNode> nodesByName = appraiseRecordRepository.findAppraiseRecordInNames(String.format(PK.PK_APPRAISE_RECORD, schoolId), academicYearId, names);
// 正常情况下 name 一一对应 todo: 临时解决
Map<String, AppraiseTreeNode> nameNodeMap = nodesByName.stream()
.collect(Collectors.toMap(AppraiseTreeNode::getName, item -> item, (existing, replacement) -> {
if (replacement.getPath() != null) {
return replacement;
}
return existing;
}));
rankVoList = rankVoList.stream()
.sorted(Comparator.comparing(RankPo::getCount).reversed())
// 流中对元素操作但不改变流
.peek(s -> {
AppraiseTreeNode node = nameNodeMap.get(s.getName());
if (node != null) {
s.setPath(node.getPath());
s.setIsPraise(node.isPraise());
}
})
.collect(Collectors.toList());
return rankVoList;
}
@Override
public List<StudentRankVo> studentRank(TimeRangeDto timeRangeDto) {
Long startTime = timeRangeDto.getStartTime();
Long endTime = timeRangeDto.getEndTime();
String academicYearId = timeRangeDto.getAcademicYearId();
String schoolId = SecurityUtil.getLoginUser().getSchoolId();
List<RankPo> rankPoList = appraiseRecordRepository.studentRank(
String.format(PK.PK_APPRAISE_RECORD, schoolId),
academicYearId,
startTime,
endTime
);
// 根据 id 分组
Map<String, List<RankPo>> idRankPoMap = rankPoList.stream().collect(Collectors.groupingBy(RankPo::getId));
final List<StudentRankVo> rankVos = new ArrayList<>();
idRankPoMap.forEach((id, list) -> {
StudentRankVo rankVo = new StudentRankVo();
int praiseCount = 0;
int criticalCount = 0;
rankVo.setId(id);
for (RankPo po : list) {
if (po.getIsPraise()) {
praiseCount += po.getCount();
} else {
criticalCount += po.getCount();
}
}
rankVo.setPraiseCount(praiseCount);
rankVo.setCriticalCount(criticalCount);
rankVo.setTotalCount(praiseCount + criticalCount);
rankVos.add(rankVo);
});
// 排序
List<StudentRankVo> res = rankVos.stream()
.sorted(Comparator.comparing(StudentRankVo::getTotalCount).reversed())
.collect(Collectors.toList());
// 设置 student name
if (ObjectUtils.isEmpty(res)) return null;
Set<String> studentIdSet = res.stream().map(StudentRankVo::getId).collect(Collectors.toSet());
if (ObjectUtils.isEmpty(studentIdSet)) return res;
List<Student> students = studentRepository.findAllByCodeAndIdIn(String.format(PK.STUDENT, schoolId), studentIdSet);
// 利用数组作为三元组,提取 Student 中的 name 和 classId, picture
Map<String, String[]> idNameMap = students.stream().collect(Collectors.toMap(Student::getId, item -> {
String[] studentInfo = new String[3];
studentInfo[0] = item.getName();
studentInfo[1] = item.getClassId();
studentInfo[2] = item.getPicture();
return studentInfo;
}));
res.forEach(rankVo -> {
rankVo.setName(idNameMap.get(rankVo.getId())[0]);
rankVo.setClassName(idNameMap.get(rankVo.getId())[1]);
rankVo.setPicture(idNameMap.get(rankVo.getId())[2]);
});
// 设置 class name
Set<String> classIds = students.stream().map(Student::getClassId).collect(Collectors.toSet());
List<ClassInfo> classes = classRepository.findAllByCodeAndIdIn(String.format(PK.CLASS, schoolId), classIds);
Map<String, String> idClassNameMap = classes.stream().collect(Collectors.toMap(ClassInfo::getId, ClassInfo::getName));
res.forEach(rankVo -> rankVo.setClassName(idClassNameMap.getOrDefault(rankVo.getClassName(), null)));
return res;
}
@Override
public List<AchievementRule> updateAchieveRule(UpdateAchievementRuleDto ruleDto) {
String periodId = ruleDto.getPeriodId();
UpdateAchievementRuleDto.UpdateRule updateRule = ruleDto.getUpdateRule();
if (ObjectUtils.isEmpty(updateRule) || StringUtils.isBlank(updateRule.getId()) || ObjectUtils.isEmpty(updateRule.getName())) {
throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "rule id/name 不能为空");
}
if (updateRule.getLevelCount() == null || updateRule.getLevelCount() <= 0 || updateRule.getPromotionLevel() == null || updateRule.getPromotionLevel() <= 0) {
throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "规则不能为空或小于等于0");
}
User user = SecurityUtil.getLoginUser();
String schoolId = user.getSchoolId();
Appraise appraise = RepositoryUtil.findOne(appraiseRepository.findRulesById(schoolId, periodId), "参数错误,找不到该学段下的评价规则");
List<AchievementRule> rules = appraise.getAchievementRules();
if (ObjectUtils.isEmpty(rules)) {
throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "该学段暂无没有成就规则");
}
// sort rules by level
rules = rules.stream().sorted(Comparator.comparing(AchievementRule::getLevel)).collect(Collectors.toList());
boolean flag = false;
int lastPromotionCount = 0;
for (int i = 0, rulesSize = rules.size(); i < rulesSize; i++) {
AchievementRule rule = rules.get(i);
if (updateRule.getId().equals(rule.getId())) {
BeanUtils.copyProperties(updateRule, rule);
lastPromotionCount = rule.getLevelCount() * rule.getPromotionLevel();
rule.setPromotionCount(lastPromotionCount);
flag = true;
continue;
}
// 处理后面的节点,将 promotionCount 依次修改
if (flag) {
lastPromotionCount = lastPromotionCount + rule.getLevelCount() * rule.getPromotionLevel();
rule.setPromotionCount(lastPromotionCount);
}
}
CosmosPatchOperations operations = CosmosPatchOperations.create().replace("/achievementRules", rules);
Appraise saved = appraiseRepository.save(appraise.getId(), new PartitionKey(PK.PK_APPRAISE), Appraise.class, operations);
return saved.getAchievementRules();
}
@Override
public List<AchievementRule> getAchieveRules(String periodId) {
User user = SecurityUtil.getLoginUser();
String schoolId = user.getSchoolId();
Appraise appraise = RepositoryUtil.findOne(appraiseRepository.findRulesById(schoolId, periodId), "还未创建该学段下的评价规则");
List<AchievementRule> rules = appraise.getAchievementRules();
if (ObjectUtils.isEmpty(rules)) {
rules = Collections.emptyList();
}
return rules;
}
}

@ -0,0 +1,242 @@
package cn.teammodel.controller.admin.service.impl;
import cn.teammodel.common.ErrorCode;
import cn.teammodel.common.PK;
import cn.teammodel.config.exception.ServiceException;
import cn.teammodel.config.ies.IESConfig;
import cn.teammodel.controller.admin.service.AdminIndexDutyService;
import cn.teammodel.model.dto.admin.appraise.TimeRangeDto;
import cn.teammodel.model.dto.weekDuty.LessonRecordDto;
import cn.teammodel.model.entity.User;
import cn.teammodel.model.entity.school.ClassInfo;
import cn.teammodel.model.entity.school.School;
import cn.teammodel.model.entity.school.Teacher;
import cn.teammodel.model.entity.weekDuty.WeekDuty;
import cn.teammodel.model.vo.admin.DutyIndexData;
import cn.teammodel.model.vo.admin.DutyIndexPo;
import cn.teammodel.model.vo.admin.DutyNodeRankVo;
import cn.teammodel.model.vo.admin.DutyRankPo;
import cn.teammodel.repository.ClassRepository;
import cn.teammodel.repository.DutyRecordRepository;
import cn.teammodel.repository.SchoolRepository;
import cn.teammodel.repository.TeacherRepository;
import cn.teammodel.security.utils.SecurityUtil;
import cn.teammodel.utils.GroupUtil;
import cn.teammodel.utils.JsonUtil;
import cn.teammodel.utils.SchoolDateUtil;
import com.azure.spring.data.cosmos.core.query.CosmosPageRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import lombok.var;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static cn.teammodel.utils.SchoolDateUtil.calculateWeekNum;
/**
* @author winter
* @create 2024-02-28 15:07
*/
@Service
@Slf4j
public class AdminIndexDutyServiceImpl implements AdminIndexDutyService {
@Resource
private SchoolRepository schoolRepository;
@Resource
private TeacherRepository teacherRepository;
@Resource
private ClassRepository classRepository;
@Resource
private DutyRecordRepository dutyRecordRepository;
@Autowired
private Environment env;
/* @Autowired
public AdminIndexDutyServiceImpl(Environment env) {
this.env = env;
}*/
@Override
public DutyIndexData getIndexData(String periodId) {
User loginUser = SecurityUtil.getLoginUser();
String schoolId = loginUser.getSchoolId();
final int SLICE_SIZE = 100;
if (StringUtils.isBlank(periodId)) {
throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "不能为空");
}
final DutyIndexData DutyIndexData = new DutyIndexData();
int totalCount = 0;
int negetiveCount = 0;
Set<String> creatorIdSet = new HashSet<>();
// todo
Set<String> classIdSet = new HashSet<>();
// 获取学期起止时间
List<School.Semester> semesters = schoolRepository.findSemestersById(schoolId, periodId);
SchoolDateUtil.semesterModel semesterModel = SchoolDateUtil.getSemesterByNow(semesters, LocalDate.now());
String academicYearId = semesterModel.getAcademicYearId();
LocalDateTime startDatetime = semesterModel.getStartDatetime();
LocalDateTime endDatetime = semesterModel.getEndDatetime();
if (startDatetime == null || endDatetime == null) throw new ServiceException(ErrorCode.PARAMS_ERROR);
long totalWeek = calculateWeekNum(startDatetime, endDatetime, null);
// slice 分段读取
CosmosPageRequest pageRequest = new CosmosPageRequest(0, SLICE_SIZE, null);
Slice<DutyIndexPo> slice;
Map<Long, Integer> countByWeek = SchoolDateUtil.createEmptyWeekMap(totalWeek);
do {
slice = dutyRecordRepository.findAllByAcademicYearId(String.format(PK.WEEK_DUTY_RECORD, schoolId), academicYearId, pageRequest);
List<DutyIndexPo> content = slice.getContent();
if (ObjectUtils.isEmpty(content)) {
break;
}
// 分批次计算
for (DutyIndexPo item : content) {
// 处理每周的评价数
long weekNum = calculateWeekNum(startDatetime, endDatetime, item.getCreateTime());
countByWeek.put(weekNum, countByWeek.getOrDefault(weekNum, 0) + 1);
// 处理总评价数
totalCount++;
// 处理批评数
if (item.getDutyTreeNodeScore() < 0) {
negetiveCount++;
}
// 处理已评价老师总数
creatorIdSet.add(item.getCreatorId());
// 处理被评价的学生数
classIdSet.add(item.getClassId());
}
if (slice.hasNext()) {
pageRequest = (CosmosPageRequest) slice.nextPageable();
}
} while (slice.hasNext());
// 组装数据
DutyIndexData.setCountByWeek(countByWeek);
DutyIndexData.setTotalCount(totalCount);
DutyIndexData.setClassCount(classIdSet.size());
DutyIndexData.setPositiveCount(totalCount - negetiveCount);
DutyIndexData.setTeacherCount(creatorIdSet.size());
return DutyIndexData;
}
@Override
public List<DutyRankPo> classRank(TimeRangeDto timeRangeDto) {
Long startTime = timeRangeDto.getStartTime();
Long endTime = timeRangeDto.getEndTime();
String academicYearId = timeRangeDto.getAcademicYearId();
String schoolId = SecurityUtil.getLoginUser().getSchoolId();
List<DutyRankPo> res = dutyRecordRepository.classRank(String.format(PK.WEEK_DUTY_RECORD, schoolId), academicYearId, startTime, endTime);
if (ObjectUtils.isEmpty(res)) return null;
List<String> classIds = res.stream().map(DutyRankPo::getId).collect(Collectors.toList());
if (ObjectUtils.isEmpty(classIds)) {
return null;
}
List<ClassInfo> classes = classRepository.findAllByCodeAndIdIn(String.format(PK.CLASS, schoolId), classIds);
Map<String, String> idNameMap = classes.stream().collect(Collectors.toMap(ClassInfo::getId, ClassInfo::getName));
// reversed sort by score
res = res.stream().peek(s -> s.setName(idNameMap.get(s.getId()))).sorted((a, b) -> b.getScore() - a.getScore()).collect(Collectors.toList());
return res;
}
@Override
public List<DutyRankPo> teacherRank(TimeRangeDto timeRangeDto) {
Long startTime = timeRangeDto.getStartTime();
Long endTime = timeRangeDto.getEndTime();
String academicYearId = timeRangeDto.getAcademicYearId();
String schoolId = SecurityUtil.getLoginUser().getSchoolId();
List<DutyRankPo> res = dutyRecordRepository.teacherRank(String.format(PK.WEEK_DUTY_RECORD, schoolId), academicYearId, startTime, endTime);
// set teacher name
if (ObjectUtils.isEmpty(res)) return null;
Set<String> teacherIdSet = res.stream().map(DutyRankPo::getId).collect(Collectors.toSet());
if (ObjectUtils.isEmpty(teacherIdSet)) return res;
List<Teacher> teachers = teacherRepository.findAllByCodeAndIdIn(PK.COMMON_BASE, teacherIdSet);
Map<String, String> idNameMap = teachers.stream().collect(Collectors.toMap(Teacher::getId, Teacher::getName));
res = res.stream().peek(s -> s.setName(idNameMap.get(s.getId()))).sorted((a, b) -> b.getScore() - a.getScore()).collect(Collectors.toList());
return res;
}
@Override
public List<DutyNodeRankVo> appraiseNodeRank(TimeRangeDto timeRangeDto) {
Long startTime = timeRangeDto.getStartTime();
Long endTime = timeRangeDto.getEndTime();
String academicYearId = timeRangeDto.getAcademicYearId();
String schoolId = SecurityUtil.getLoginUser().getSchoolId();
List<DutyNodeRankVo> res = dutyRecordRepository.dutyNodeRank(String.format(PK.WEEK_DUTY_RECORD, schoolId), academicYearId, startTime, endTime);
if (ObjectUtils.isEmpty(res)) return null;
List<String> names = res.stream().map(DutyNodeRankVo::getName).collect(Collectors.toList());
if (ObjectUtils.isEmpty(names)) return null;
// 去重后的 nodes
List<WeekDuty.DutyTreeNode> nodesByName = dutyRecordRepository.findDutyRecordInNames(String.format(PK.WEEK_DUTY_RECORD, schoolId), academicYearId, names);
// 正常情况下 name 一一对应 // TODO: 2024/2/29
Map<String, WeekDuty.DutyTreeNode> nameNodeMap = nodesByName.stream()
.collect(Collectors.toMap(WeekDuty.DutyTreeNode::getName, item -> item, (existing, replacement) -> {
if (replacement.getPath() != null) {
return replacement;
}
return existing;
}));
res = res.stream()
.sorted(Comparator.comparing(DutyNodeRankVo::getCount).reversed())
// 流中对元素操作但不改变流
.peek(s -> {
WeekDuty.DutyTreeNode node = nameNodeMap.get(s.getName());
if (node != null) {
s.setPath(node.getPath());
s.setScore(node.getScore());
}
})
.collect(Collectors.toList());
return res;
}
@Override
public Map<String, Object> getLessonRecord(LessonRecordDto lessonRecordDto, HttpServletRequest request) {
Map<String, Object> mapper = new HashMap<>();
String url = env.getProperty("ies.server-url");
if(lessonRecordDto.isClassMeeting()) {
List<School.Subject> subjects = schoolRepository.findSubjectById(lessonRecordDto.getSchool(), lessonRecordDto.getPeriodId());
List<String> subjectIds = subjects.stream().filter(subject -> "班会".equals(subject.getName()))
.map(School.Subject::getId).collect(Collectors.toList());
lessonRecordDto.setSubjectId(subjectIds);
}
mapper = GroupUtil.getGroupId(lessonRecordDto,new GroupUtil(env), request,url);
return mapper;
}
}

@ -0,0 +1,104 @@
package cn.teammodel.controller.admin.service.impl;
import cn.teammodel.controller.admin.service.ArtService;
import cn.teammodel.model.dto.admin.art.ArtFindDto;
import cn.teammodel.model.dto.admin.common.GroupDto;
import cn.teammodel.model.dto.admin.common.RGroupList;
import cn.teammodel.model.dto.admin.common.RMember;
import cn.teammodel.model.vo.admin.ArtElementsVo;
import cn.teammodel.repository.ArtRepository;
import cn.teammodel.repository.ClassRepository;
import cn.teammodel.repository.StudentRepository;
import cn.teammodel.utils.GroupUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
@Service
public class ArtServiceImpl implements ArtService {
@Resource
private ArtRepository artRepository;
@Resource
private ClassRepository classRepository;
@Resource
private StudentRepository studentRepository;
@Autowired
private Environment env;
@Override
public List<ArtElementsVo> getArtList(ArtFindDto artFindDto, HttpServletRequest request) {
List<ArtElementsVo> artElementsVos = artRepository.findPeriodById(artFindDto.getPeriodId(),"Art-"+ artFindDto.getCode());
List<String> classIds = artElementsVos.stream()
.map(ArtElementsVo::getClasses) // 正确的方法引用
.flatMap(List::stream) // 将内部列表扁平化
.collect(Collectors.toList());
GroupDto groupDto = new GroupDto();
groupDto.setIds(classIds);
groupDto.setSchoolId(artFindDto.getCode());
String url = env.getProperty("ies.server-url-group");
//List<ClassInfo> classes = classRepository.findAllByCodeAndIdIn("Class-"+artFindDto.getCode(),classIds);
Map<String, Object> groupId = GroupUtil.getGroupId(groupDto,new GroupUtil(env), request,url);
List<RGroupList> rGroupList = new ArrayList<>();
List<RMember> rMembers = new ArrayList<>();
for (Map.Entry<String, Object> entry : groupId.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (key.equals("groups")) {
String jsonGroups = JSON.toJSONString(value);
rGroupList = JSON.parseObject(jsonGroups, new TypeReference<List<RGroupList>>() {});
}
if (key.equals("members")) {
String jsonGroups = JSON.toJSONString(value);
rMembers = JSON.parseObject(jsonGroups, new TypeReference<List<RMember>>() {});
}
}
try {
for (ArtElementsVo artElementsVo : artElementsVos) {
List<String> classes1 = artElementsVo.getClasses();
//int stuInClassCount = 0;
for(String classId : classes1) {
//stuInClassCount += studentRepository.countByClassIdAndCode(classId, String.format(PK.STUDENT, artFindDto.getCode()));
ArtElementsVo.ClassInfos classInfos = new ArtElementsVo.ClassInfos();
rGroupList.stream().filter(rGroupList1 -> rGroupList1.getId().equals(classId)).findFirst().ifPresent(rGroupList1 -> {
classInfos.setId(rGroupList1.getId());
classInfos.setName(rGroupList1.getName());
});
artElementsVo.addClassesInfos(classInfos);
}
artElementsVo.setCount(rMembers.size());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return artElementsVos;
}
@Override
public List<RGroupList> getGroupList(GroupDto groupDto, HttpServletRequest request) {
List<RGroupList> rGroupList = new ArrayList<>();
String url = env.getProperty("ies.server-url-group");
try {
Map<String, Object> groupId = GroupUtil.getGroupId(groupDto,new GroupUtil(env), request,url);
//List<RMember> rMembers = new ArrayList<>();
for (Map.Entry<String, Object> entry : groupId.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (key.equals("groups")) {
String jsonGroups = JSON.toJSONString(value);
rGroupList = JSON.parseObject(jsonGroups, new TypeReference<List<RGroupList>>() {});
}
}
}catch (Exception e) {
throw new RuntimeException(e);
}
return rGroupList;
}
}

@ -0,0 +1,83 @@
package cn.teammodel.controller.admin.service.impl;
import cn.teammodel.controller.admin.service.CommonService;
import cn.teammodel.model.dto.admin.common.GCDto;
import cn.teammodel.model.entity.school.ClassInfo;
import cn.teammodel.model.entity.school.School;
import cn.teammodel.model.vo.admin.GradeAndClassVo;
import cn.teammodel.repository.ClassRepository;
import cn.teammodel.repository.SchoolRepository;
import cn.teammodel.utils.MonthToNumberConverter;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static java.time.LocalDate.now;
@Service
public class CommonServiceImpl implements CommonService {
@Resource
private SchoolRepository schoolRepository;
@Resource
private ClassRepository classRepository;
@Override
public List<GradeAndClassVo> getGradeAndClass(GCDto gcDto) {
List<GradeAndClassVo> gradeAndClassVos = new ArrayList<>();
try {
//获取当前学校该学段下详细信息
List<School.Period> period = schoolRepository.findPeriodById(gcDto.getSchoolId(), gcDto.getPeriodId());
List<ClassInfo> classes = classRepository.findClassBySchoolIdAndPeriodId(gcDto.getSchoolId(), gcDto.getPeriodId());
int year = now().getYear();
Month month = now().getMonth();
int mon = MonthToNumberConverter.convertMonthToNumber(month.name());
int day = now().getDayOfMonth();
//处理年级ID
for (ClassInfo classInfo : classes) {
if(period.get(0).getId().equalsIgnoreCase(classInfo.getPeriodId())) {
for (School.Semester semester : period.get(0).getSemesters()) {
int time = 0;
if(semester.getStart() == 1) {
if (mon == semester.getMonth())
{
time = day >= semester.getDay() ? 0 : 1;
}
else
{
time = mon > semester.getMonth() ? 0 : 1;
}
int eYear = year - time;
classInfo.setGrade(eYear- classInfo.getYear());
}
}
}
}
List<String> grades = period.get(0).getGrades();
int index = 0;
for (String grade : grades) {
GradeAndClassVo gradeAndClassVo = new GradeAndClassVo();
gradeAndClassVo.setGradeId(index);
gradeAndClassVo.setGradeName(grade);
classes.stream().filter(classInfo -> classInfo.getGrade() == gradeAndClassVo.getGradeId()).forEach(classInfo -> {
GradeAndClassVo.CI ci = new GradeAndClassVo.CI();
ci.setClassId(classInfo.getId());
ci.setClassName(classInfo.getName());
gradeAndClassVo.AddClass(ci);
});
index ++;
gradeAndClassVos.add(gradeAndClassVo);
}
return gradeAndClassVos;
}catch (Exception e) {
throw new RuntimeException("获取当前学校当前学段年级班级信息失败");
}
}
}

@ -0,0 +1,170 @@
package cn.teammodel.controller.frontend;
import cn.teammodel.common.IdRequest;
import cn.teammodel.common.R;
import cn.teammodel.model.dto.ai.*;
import cn.teammodel.model.entity.TmdUserDetail;
import cn.teammodel.model.entity.ai.ChatApp;
import cn.teammodel.model.entity.ai.ChatSession;
import cn.teammodel.security.utils.SecurityUtil;
import cn.teammodel.service.ChatAppService;
import cn.teammodel.service.ChatMessageService;
import cn.teammodel.service.ChatSessionService;
import io.jsonwebtoken.Claims;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/ai")
@Api(tags = "AI 能力")
public class AiController {
@Resource
private ChatSessionService chatSessionService;
@Resource
private ChatMessageService chatMessageService;
@Resource
private ChatAppService chatAppService;
@PostMapping("api/chat/completion")
@ApiOperation("与 spark 的流式对话")
public SseEmitter chatCompletionToApi(@RequestBody @Valid ChatCompletionReqDto chatCompletionReqDto) {
String userId = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().getSubject();
return chatMessageService.chatCompletion(chatCompletionReqDto, userId);
}
@PostMapping("chat/completion")
@ApiOperation("与 spark 的流式对话")
public SseEmitter chatCompletion(@RequestBody @Valid ChatCompletionReqDto chatCompletionReqDto) {
return chatMessageService.chatCompletion(chatCompletionReqDto, null);
}
// @PostMapping("chat/test/completion")
@ApiOperation("与 spark 的流式对话")
public SseEmitter testCompletion(@RequestBody @Valid ChatCompletionReqDto chatCompletionReqDto) throws IOException, InterruptedException {
SseEmitter sseEmitter = new SseEmitter();
CompletableFuture.runAsync(() -> {
try {
sseEmitter.send("曾经以为我们");
Thread.sleep(1000);
sseEmitter.send("开始了就会走到最后,可是当分手之后才明白我想的");
Thread.sleep(1000);
sseEmitter.send("你不一定能做到,当我开始去遗忘我们");
Thread.sleep(1500);
sseEmitter.send("的经历时,却不知道遗忘已变成另一种开始,");
Thread.sleep(800);
sseEmitter.send("回忆是淡了,可是痛却还在,还是最真实。放手的时候微笑着说无所谓");
Thread.sleep(800);
sseEmitter.send(",离开了你我还可以过的很好");
Thread.sleep(1000);
sseEmitter.send(",而你却不知道微笑只是用来掩盖疼痛的伤疤。");
Thread.sleep(1200);
sseEmitter.send("离开你之后的自己陷入一种无助的状态,空洞的");
Thread.sleep(1300);
sseEmitter.send("双眼回忆不起曾经的你,那时候以为与世隔绝或许才是维护自己的最好方式。");
Thread.sleep(1100);
sseEmitter.send("[DONE]");
sseEmitter.complete();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
});
return sseEmitter;
}
@GetMapping("api/session/my")
@ApiOperation("查询我的聊天会话")
public R<List<ChatSession>> listMySession() {
String userId = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().getSubject();
List<ChatSession> sessions = chatSessionService.listMySession(userId);
return R.success(sessions);
}
@GetMapping("api/chat/history/{sessionId}")
@ApiOperation("查询我的聊天记录")
public R<List<ChatSession.Message>> getHistory(@PathVariable String sessionId) {
String userId = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().getSubject();
List<ChatSession.Message> history = chatSessionService.listHistory(sessionId, userId);
return R.success(history);
}
@PostMapping("api/session/create")
@ApiOperation("创建聊天会话")
public R<String> createSession() {
String userId = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().getSubject();
String name = (String) ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().get("name");
name = StringUtils.isBlank(name) ? "老师" : name;
String sessionId = chatSessionService.createSession(userId, name);
return R.success(sessionId);
}
@PostMapping("api/session/remove")
@ApiOperation("删除聊天会话")
public R<String> removeSession(@RequestBody @Valid IdRequest idRequest) {
String userId = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().getSubject();
chatSessionService.deleteSession(idRequest.getId(), userId);
return R.success("删除会话成功");
}
@PostMapping("api/session/update")
@ApiOperation("更新聊天会话")
public R<ChatSession> updateSession(@RequestBody @Valid UpdateSessionDto updateSessionDto) {
String userId = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().getSubject();
ChatSession session = chatSessionService.updateSession(updateSessionDto, userId);
return R.success(session);
}
@PostMapping("app/list")
@ApiOperation("查询聊天应用列表")
public R<List<ChatApp>> listApp(@RequestBody @Valid SearchAppDto searchAppDto) {
List<ChatApp> chatApps = chatAppService.listApp(searchAppDto);
return R.success(chatApps);
}
@PostMapping("app/create")
@ApiOperation("创建聊天应用")
public R<ChatApp> createApp(@RequestBody @Valid CreateChatAppDto createChatAppDto) {
ChatApp chatApp = chatAppService.createApp(createChatAppDto);
return R.success(chatApp);
}
@PostMapping("app/update")
@ApiOperation("更新聊天应用")
public R<ChatApp> updateApp(@RequestBody @Valid UpdateChatAppDto updateChatAppDto) {
ChatApp chatApp = chatAppService.updateApp(updateChatAppDto);
return R.success(chatApp);
}
@PostMapping("app/remove")
@ApiOperation("删除聊天应用")
public R<String> updateApp(@RequestBody @Valid IdRequest idRequest) {
chatAppService.deleteApp(idRequest);
return R.success("删除应用成功");
}
@PostMapping("chat/comments")
@ApiOperation("设置评语")
public SseEmitter chatComments(@RequestBody @Valid ChatCompletionReqDto chatCompletionReqDto) {
/*
Authentication user0 = SecurityUtil.getAuthentication();
Object user01 = SecurityUtil.getAuthentication().getPrincipal();
TmdUserDetail user02 = (TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal();
Claims user03 = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims();
String user04 = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().getSubject();
*/
//String userId = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getClaims().getSubject();
// 获取getClaims时为空
String userId = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getUser().getId();
String userName = ((TmdUserDetail) SecurityUtil.getAuthentication().getPrincipal()).getUser().getName();
return chatMessageService.chatComments(chatCompletionReqDto, userId, userName);
}
}

@ -0,0 +1,94 @@
package cn.teammodel.controller.frontend;
import cn.teammodel.common.IdRequest;
import cn.teammodel.common.R;
import cn.teammodel.model.dto.Appraise.*;
import cn.teammodel.model.entity.appraise.Appraise;
import cn.teammodel.model.vo.appraise.AppraiseRecordVo;
import cn.teammodel.model.vo.appraise.StudentReportVo;
import cn.teammodel.service.EvaluationService;
import com.itextpdf.text.DocumentException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
/**
* @author winter
* @create 2023-11-22 15:10
*/
@RestController
@RequestMapping("/appraise")
@Api(tags = "学生评价")
public class AppraiseController {
@Resource
private EvaluationService evaluationService;
@PostMapping("getTrees")
@ApiOperation(value = "获取评价树", notes = "获取评价树")
public R<Appraise> getEvaluateTree(@RequestBody @Valid GetEvaluateTreeDto getEvaluateTreeDto) {
Appraise appraise = evaluationService.getTree(getEvaluateTreeDto);
return R.success(appraise);
}
@PostMapping("insertNode")
@ApiOperation(value = "新增评价树的节点")
public R<Appraise> insertNode(@RequestBody @Valid InsertNodeDto insertNodeDto) {
Appraise appraise = evaluationService.insertNode(insertNodeDto);
return R.success(appraise);
}
@PostMapping("updateNode")
@ApiOperation(value = "更新评价树的节点", notes = "传递更新后的节点,而不是局部更新的值")
public R<Appraise> updateTree(@RequestBody @Valid UpdateNodeDto updateNodeDto) {
// fixme: 更新一二级节点应该同时更新三级的 path
Appraise appraise = evaluationService.updateNode(updateNodeDto);
return R.success(appraise);
}
@PostMapping("deleteNode")
@ApiOperation(value = "删除评价树的节点")
public R<Appraise> deleteNode(@RequestBody @Valid DeleteNodeDto deleteNodeDto) {
Appraise appraise = evaluationService.deleteNode(deleteNodeDto);
return R.success(appraise);
}
@PostMapping("vote")
@ApiOperation(value = "给某个学生评价(投票)")
public R<String> vote(@RequestBody @Valid AppraiseVoteDto appraiseVoteDto) {
evaluationService.vote(appraiseVoteDto);
return R.success("评价成功");
}
@PostMapping("recallVote")
@ApiOperation(value = "撤回给某个学生评价(投票)")
public R<String> recallVote(@RequestBody @Valid RecallVoteDto recallVoteDto) {
evaluationService.recallVote(recallVoteDto);
return R.success("撤回评价成功");
}
@PostMapping("findVoteRecord")
@ApiOperation(value = "多条件查询当前登录老师的学生评价(投票)")
public R<List<AppraiseRecordVo>> findMyVoteRecord(@Valid @RequestBody FindVoteRecordDto findVoteRecordDto) {
List<AppraiseRecordVo> res = evaluationService.findVoteRecord(findVoteRecordDto);
return R.success(res);
}
@PostMapping("studentReport")
@ApiOperation(value = "查看学生当前的学期的实时评价报告")
public R<StudentReportVo> studentReport(@Valid @RequestBody IdRequest idRequest) {
StudentReportVo res = evaluationService.studentReport(idRequest);
return R.success(res);
}
@PostMapping("studentReportPDF")
@ApiOperation(value = "导出学生当前的学期的实时评价报告 PDF")
public void exportStuReportPdf(@Valid @RequestBody IdRequest idRequest, HttpServletResponse response) throws DocumentException, IOException {
evaluationService.exportStuReportPdf(idRequest, response);
}
}

@ -0,0 +1,93 @@
package cn.teammodel.controller.frontend;
import cn.teammodel.common.IdRequest;
import cn.teammodel.common.R;
import cn.teammodel.model.dto.weekDuty.*;
import cn.teammodel.model.entity.weekDuty.WeekDuty;
import cn.teammodel.model.vo.weekDuty.DutyRecordVo;
import cn.teammodel.service.DutyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* @author winter
* @create 2024-01-03 10:06
*/
@RestController
@RequestMapping("/duty")
@Api(tags = "值周巡检")
public class DutyController {
@Resource
private DutyService dutyService;
@GetMapping("/getTree")
@ApiOperation("获取值周评价标准树(不存在则拷贝模板)")
public R<WeekDuty> getTree() {
WeekDuty weekDuty = dutyService.getTree();
return R.success(weekDuty);
}
@PostMapping("/insertNode")
@ApiOperation("插入值周评价标准树节点")
public R<WeekDuty> insertNode(@RequestBody @Valid InsertDutyNodeDto insertDutyNodeDto) {
WeekDuty weekDuty = dutyService.insertNode(insertDutyNodeDto);
return R.success(weekDuty);
}
@PostMapping("/deleteNode")
@ApiOperation("删除值周评价标准树节点")
public R<WeekDuty> deleteNode(@RequestBody @Valid DeleteDutyNodeDto deleteDutyNodeDto) {
WeekDuty weekDuty = dutyService.deleteNode(deleteDutyNodeDto);
return R.success(weekDuty);
}
@PostMapping("/updateNode")
@ApiOperation("更新值周评价标准树节点")
public R<WeekDuty> updateNode(@RequestBody @Valid UpdateDutyNodeDto updateDutyNodeDto) {
WeekDuty weekDuty = dutyService.updateNode(updateDutyNodeDto);
return R.success(weekDuty);
}
@PostMapping("/vote")
@ApiOperation("值周评价投票")
public R<String> vote(@RequestBody @Valid DutyVoteDto dutyVoteDto) {
dutyService.vote(dutyVoteDto);
return R.success("评价成功");
}
@PostMapping("/recallVote")
@ApiOperation("撤回评价投票")
public R<String> recallVote(@RequestBody @Valid RecallDutyVoteDto recallDutyVoteDto) {
dutyService.recallVote(recallDutyVoteDto);
return R.success("撤回评价成功");
}
@PostMapping("/insertSpot")
@ApiOperation("插入值周评价地点")
public R<List<WeekDuty.DutySpot>> insertSpot(@RequestBody @Valid InsertSpotDto insertSpotDto) {
List<WeekDuty.DutySpot> spots = dutyService.insertSpot(insertSpotDto);
return R.success(spots);
}
@PostMapping("/deleteSpot")
@ApiOperation("删除值周评价地点")
public R<List<WeekDuty.DutySpot>> deleteSpot(@RequestBody @Valid IdRequest idRequest) {
List<WeekDuty.DutySpot> spots = dutyService.deleteSpot(idRequest.getId());
return R.success(spots);
}
@PostMapping("/findRecords")
@ApiOperation(value = "多条件查询评价明细", notes = "只带 periodId 默认查询当前登录老师在本周的评价明细")
public R<List<DutyRecordVo>> findRecords(@RequestBody @Valid FindDutyRecordDto findDutyRecordDto) {
List<DutyRecordVo> items = dutyService.findRecords(findDutyRecordDto);
return R.success(items);
}
}

@ -0,0 +1,76 @@
package cn.teammodel.controller.frontend;
import cn.teammodel.common.R;
import cn.teammodel.repository.AppraiseRepository;
import cn.teammodel.service.EvaluationService;
import com.itextpdf.text.DocumentException;
import io.swagger.annotations.Api;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
@RequestMapping("/")
@Api(tags = "鉴权测试")
public class HelloController {
@Resource
private EvaluationService evaluationService;
@Resource
private AppraiseRepository appraiseRepository;
@GetMapping("hello")
@PreAuthorize("@ss.hasRole('admin')")
public R<String> hello() {
System.out.println(SecurityContextHolder.getContext().getAuthentication());
return new R(200, "success","hello world");
}
@GetMapping("public/free")
@PreAuthorize("permitAll()")
public R<String> free() {
return new R(200, "success","hello world");
}
@GetMapping("public/pdf")
public void freepdf(HttpServletResponse response) throws DocumentException, IOException { // 设置response参数
// response.reset();
// response.setContentType("application/pdf");
// response.setHeader("Content-disposition",
// "attachment;filename=report_student_" + System.currentTimeMillis() + ".pdf");
// ClassPathResource resource = new ClassPathResource("templates/pdf_templates/student_report.pdf");
// InputStream in = resource.getInputStream();
// ServletOutputStream os = response.getOutputStream();
// // 处理 stampter
// PdfReader pdfReader = new PdfReader(in);
// PdfStamper stamper = new PdfStamper(pdfReader, os);
//
// Map<String, String> data = PdfUtil.data();
// DefaultPieDataset dataset = new DefaultPieDataset( );
// dataset.setValue( "IPhone 5s" , new Double( 20 ) );
// dataset.setValue( "SamSung Grand" , new Double( 20 ) );
// dataset.setValue( "MotoG" , new Double( 40 ) );
// dataset.setValue( "Nokia Lumia" , new Double( 10 ) );
//
// JFreeChart pieChart = ChartUtil.pieChart("手机销量统计", dataset);
// ByteArrayOutputStream bos = new ByteArrayOutputStream();
// ChartUtils.writeChartAsJPEG(bos, pieChart, 850, 440);
// // 填充表单
// PdfUtil.fillPdfForm(stamper, data);
// PdfUtil.fillImage(stamper, "praiseDistribution", bos.toByteArray());
// PdfUtil.fillImage(stamper, "criticalDistribution", bos.toByteArray());
// // 关闭流
// stamper.setFormFlattening(true);
// stamper.close();
// os.close();
//
}
}

@ -0,0 +1,63 @@
package cn.teammodel.controller.frontend;
import cn.teammodel.common.IdRequest;
import cn.teammodel.common.R;
import cn.teammodel.model.dto.news.CreateNewsDto;
import cn.teammodel.model.dto.news.UpdateNewsDto;
import cn.teammodel.model.entity.news.News;
import cn.teammodel.service.NewsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* @author winter
* @create 2024-02-26 17:04
*/
@RestController
@RequestMapping("/news")
@Api(tags = "新闻设置")
public class NewsController {
@Resource
private NewsService newsService;
@GetMapping("list/{periodId}")
@ApiOperation("查询新闻")
public R<List<News>> listNews(@PathVariable String periodId) {
List<News> Newss = newsService.listNews(periodId);
return R.success(Newss);
}
@GetMapping("get/{newsId}")
@ApiOperation("根据id查询新闻")
public R<News> getNewsById(@PathVariable String newsId) {
News news = newsService.getNewsById(newsId);
return R.success(news);
}
@PostMapping("create")
@ApiOperation("创建新闻")
public R<News> createNews(@RequestBody @Valid CreateNewsDto createNewsDto) {
News News = newsService.createNews(createNewsDto);
return R.success(News);
}
@PostMapping("update")
@ApiOperation("更新新闻")
public R<News> updateNews(@RequestBody @Valid UpdateNewsDto updateNewsDto) {
News News = newsService.updateNews(updateNewsDto);
return R.success(News);
}
@PostMapping("remove")
@ApiOperation("删除新闻")
public R<String> deleteNews(@RequestBody @Valid IdRequest idRequest) {
newsService.deleteNews(idRequest);
return R.success("删除新闻成功");
}
}

@ -0,0 +1,44 @@
package cn.teammodel.manager.notification;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
*
* @author winter
* @create 2023-11-14 10:11
*/
@Component
@Slf4j
public class DingAlertNotifier implements NotificationService{
private final DingTalkClient client;
@Autowired
public DingAlertNotifier(@Value("${ding.server-url}") String dingServerUrl) {
this.client = new DefaultDingTalkClient(dingServerUrl);
}
@Override
public void send(String message) {
OapiRobotSendRequest request = new OapiRobotSendRequest();
// 文本消息
request.setMsgtype("text");
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(message);
request.setText(text);
// at 管理员提醒异常
OapiRobotSendRequest.At atAll = new OapiRobotSendRequest.At();
atAll.setIsAtAll(true);
request.setAt(atAll);
try {
client.execute(request);
} catch (ApiException e) {
log.error("钉钉 robot 推送消息渠道异常: {}", e.getMessage());
}
}
}

@ -0,0 +1,16 @@
package cn.teammodel.manager.notification;
/**
*
* @author winter
* @create 2023-11-14 10:08
*/
public interface NotificationService {
/**
*
* @author: winter
* @date: 2023/11/14 10:09
*/
void send(String message);
}

@ -0,0 +1,12 @@
package cn.teammodel.manager.wx;
import org.springframework.stereotype.Service;
/**
* @author winter
* @create 2024-03-26 11:08
*/
@Service
public class MiniProgramSevice {
}

@ -0,0 +1,33 @@
package cn.teammodel.model.dto.Appraise;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-11-28 16:16
*/
@Data
public class AppraiseVoteDto {
@NotNull
@ApiModelProperty(value = "评价对象 Id")
private String targetId;
@NotNull
@ApiModelProperty(value = "评价对象类型:", allowableValues = "student ,class")
private String targetType;
@ApiModelProperty(value = "评分是否传播到班级下的所有学生(评价 CLASS 时生效)")
private boolean spread;
@ApiModelProperty(value = "是否推送给家长(评价 STUDENT 时生效)")
private boolean pushParent;
/**
* id
*/
@NotNull
@ApiModelProperty(value = "评价项唯一 id", required = true)
private String appraiseId;
}

@ -0,0 +1,17 @@
package cn.teammodel.model.dto.Appraise;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-11-22 16:16
*/
@Data
public class DeleteNodeDto {
@NotNull(message = "学段 id 不能为空")
String periodId;
@NotNull(message = "评价项节点 id 不能为空")
String id;
}

@ -0,0 +1,32 @@
package cn.teammodel.model.dto.Appraise;
import cn.teammodel.common.PageableRequest;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-11-28 16:16
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class FindVoteRecordDto extends PageableRequest {
@NotNull
@ApiModelProperty(value = "必要参数,用于获取当前学年,注意: 其他参数不传则默认获取登录老师在该学年下评价的所有记录", required = true)
private String periodId;
@ApiModelProperty(value = "班级或学生 id")
private String targetId;
@ApiModelProperty(value = "评价对象类型:", allowableValues = "student ,class")
private String targetType;
@ApiModelProperty(value = "按班级 id 搜索")
private String classId;
@ApiModelProperty(value = "是否为表扬")
private Boolean isPraise;
}

@ -0,0 +1,17 @@
package cn.teammodel.model.dto.Appraise;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-11-22 15:21
*/
@Data
public class GetEvaluateTreeDto {
@ApiModelProperty(value = "学段 id", required = true)
@NotNull(message = "学段 id 不能为空")
String periodId;
}

@ -0,0 +1,27 @@
package cn.teammodel.model.dto.Appraise;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-11-22 16:16
*/
@Data
public class InsertNodeDto {
@ApiModelProperty(value = "学段 id", required = true)
@NotNull(message = "学段 id 不能为空")
String periodId;
@ApiModelProperty(value = "父亲节点,不传则为根节点")
String pid;
@ApiModelProperty(value = "父亲节点,不传则为根节点", required = true)
@NotBlank(message = "name 不能为空")
String name;
String logo;
Integer order = 0;
Integer score = 0;
Boolean isPraise = true;
}

@ -0,0 +1,20 @@
package cn.teammodel.model.dto.Appraise;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-11-22 16:16
*/
@Data
public class RecallVoteDto {
@NotNull
@ApiModelProperty(value = "学生评价记录的文档id", required = true)
String recordId;
@NotNull
@ApiModelProperty(value = "学生评价记录的具体节点id", required = true)
String nodeId;
}

@ -0,0 +1,23 @@
package cn.teammodel.model.dto.Appraise;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-11-22 16:16
*/
@Data
public class UpdateNodeDto {
@ApiModelProperty(value = "学段 id", required = true)
@NotNull(message = "学段 id 不能为空")
String periodId;
@ApiModelProperty(value = "评价项节点的 id")
String id;
String name;
String logo;
Integer order;
boolean isPraise;
}

@ -0,0 +1,17 @@
package cn.teammodel.model.dto.admin.appraise;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-12-06 19:03
*/
@Data
public class TimeRangeDto {
private Long startTime;
private Long endTime;
@NotNull
// todo: 似乎不需要(有时间范围就不需要这个字段来划分时间,如果不传时间那就需要)
private String academicYearId;
}

@ -0,0 +1,34 @@
package cn.teammodel.model.dto.admin.appraise;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-12-13 17:34
*/
@Data
public class UpdateAchievementRuleDto {
@NotNull
@ApiModelProperty("学段 id")
private String periodId;
@ApiModelProperty("更新的 rule 节点: 将会直接覆盖老节点")
private UpdateRule updateRule;
@Data
public static class UpdateRule {
private String id;
/**
*
*/
private String name = "";
@ApiModelProperty("等级 logo")
private String logo = "";
@ApiModelProperty("每次所需表扬数")
private Integer levelCount = 0;
@ApiModelProperty("晋级所需下一等级所需当前等级次数")
private Integer promotionLevel = 0;
}
}

@ -0,0 +1,21 @@
package cn.teammodel.model.dto.admin.art;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
public class ArtFindDto
{
@ApiModelProperty("学校编码")
private String code;
@ApiModelProperty("学段ID")
private String periodId;
@ApiModelProperty("学段类型")
private String periodType;
@ApiModelProperty("开始时间")
private Long startTime;
@ApiModelProperty("结束时间")
private Long endTime;
}

@ -0,0 +1,9 @@
package cn.teammodel.model.dto.admin.common;
import lombok.Data;
@Data
public class GCDto {
private String schoolId;
private String periodId;
}

@ -0,0 +1,11 @@
package cn.teammodel.model.dto.admin.common;
import lombok.Data;
import java.util.List;
@Data
public class GroupDto {
public List<String> ids;
public String schoolId;
}

@ -0,0 +1,73 @@
package cn.teammodel.model.dto.admin.common;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@Data
public class RGroupList {
public String id ;
public String code ;
public String pk ;
public String name ;
//标记该名单唯一code
public String no ;
public String periodId ;
//课程id,需要标记则标记
//public String courseId ;
public String scope ;
public String school ;
public String creatorId ;
/// <summary>
///研修培训名单yxtrain 教学班teach 行政班学生搜寻classId动态返回class ,教研组research学科组学科搜寻动态返回subject好友friend管理manage群组group等,"activity",
///TeacherAll 全体教师StudentAll全体学生 TchStuAll全体师生 动态返回
/// </summary>
public String type ;
public int year ;
/// <summary>
/// 名单过期时间。
/// </summary>
public long expire ;
/// <summary>
/// 醍摩豆id成员数量
/// </summary>
public int tcount ;
/// <summary>
/// 校内账号成员数量
/// </summary>
public int scount ;
public List<RMember> members = new ArrayList<>();
public String leader ;
public int froms ;
/// <summary>
/// 个人名单是否开放 加入。0 不允许1 允许。
/// </summary>
public int joinLock ;
/// <summary>
///补充毕业0在校1毕业
/// </summary>
public int graduate ;
/// <summary>
/// 是否开启审核0未开启1开启。
/// </summary>
public int review ;
/// <summary>
/// 加入人数200人学生加入已满200 自动关闭加入。可手动解除限制,开启审核时,关闭人数上限设置机制
/// </summary>
public int limitCount ;
/// <summary>
/// 自选座号 0 不允许1 运行
/// </summary>
public int optNo ;
/// <summary>
/// 二维码过期时间
/// </summary>
public long qrcodeExpire ;
/// <summary>
/// 二维码 天数
/// </summary>
public int qrcodeDays ;
public HashSet<Integer> grades = new HashSet<Integer>();
}

@ -0,0 +1,73 @@
package cn.teammodel.model.dto.admin.common;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class RMember {
/// <summary>
/// 账号id
/// </summary>
public String id ;
//学生所在的学校
public String code ;
//兼容HiTeach 需要的字段
public String schoolId ;
//学生所在的学校
public String schoolName ;
/// <summary>
/// 名称
/// </summary>
public String name ;
/// <summary>
///类型 1 tmdid,2 student
/// </summary>
public int type ;
/// <summary>
/// 头像
/// </summary>
public String picture ;
/// <summary>
/// 性别 M( male,男) F (female 女) N(secret 保密)
/// </summary>
public String gender ;
/// <summary>
///座号
/// </summary>
public String no ;
/// <summary>
/// IRS WebIRS编号。
/// </summary>
public String irs ;
public String tag ;
/// <summary>
/// 行政班
/// </summary>
public String classId ;
/// <summary>
/// 名单分组id
/// </summary>
public String groupId ;
/// <summary>
/// 名单分组名称
/// </summary>
public String groupName ;
public String nickname ;
//补充毕业
//0在校1毕业
public int graduate ;
//所在名单集合
public List<String> groupListIds = new ArrayList<String>();
public int year ;
public String periodId ;
/// <summary>
/// 0 自动的座号和irs,1 手动的irs
/// </summary>
public int manual ;
}

@ -0,0 +1,27 @@
package cn.teammodel.model.dto.admin.weekduty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2024-01-15 10:30
*/
@Data
public class AdminFindDutyRecordDto {
@ApiModelProperty("老师 id")
private String teacherId;
@ApiModelProperty(value = "班级 id", notes = "若不携带默认不指定某个班级")
private String classId;
@ApiModelProperty(required = true)
@NotNull(message = "academicYearid 不能为空")
private String academicYearId;
@ApiModelProperty(value = "是否加分", notes = "若不携带默认为全部")
private Boolean positive;
@ApiModelProperty(value = "起始时间", notes = "若不携带默认为当前周")
private Long startTime;
@ApiModelProperty(value = "结束时间", notes = "若不携带默认为当前周")
private Long endTime;
}

@ -0,0 +1,16 @@
package cn.teammodel.model.dto.ai;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class ChatCompletionReqDto {
private String sessionId;
/**
* id
*/
private String appId;
@NotBlank(message = "请输入消息内容")
private String text;
}

@ -0,0 +1,30 @@
package cn.teammodel.model.dto.ai;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class CreateChatAppDto {
@ApiModelProperty("应用图标")
private String icon;
@ApiModelProperty("应用名称")
@NotBlank(message = "请输入应用名称")
private String name;
private String lang;
private String bizType;
@ApiModelProperty("应用域")
@NotBlank(message = "请输入应用域")
private String scope;
private String itemType;;
private String period;
private String subject;
@ApiModelProperty("应用描述")
private String description;
@NotBlank(message = "请输入应用提示词")
@ApiModelProperty("应用提示词")
private String prompt;
}

@ -0,0 +1,20 @@
package cn.teammodel.model.dto.ai;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author winter
* @create 2024-02-01 15:40
*/
@Data
public class SearchAppDto {
@NotBlank(message = "scope不能为空")
private String scope;
private String bizType;
private String lang;
private String itemType;;
private String period;
private String subject;
}

@ -0,0 +1,34 @@
package cn.teammodel.model.dto.ai;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class UpdateChatAppDto {
@ApiModelProperty("应用 id")
@NotBlank(message = "请输入应用 id")
private String id;
@ApiModelProperty("应用图标")
private String icon;
@ApiModelProperty("应用名称")
@NotBlank(message = "请输入应用名称")
private String name;
private String lang;
private String bizType;
@ApiModelProperty("应用域")
@NotBlank(message = "请输入应用域")
private String scope;
private String itemType;;
private String period;
private String subject;
@ApiModelProperty("应用描述")
private String description;
@NotBlank(message = "请输入应用提示词")
@ApiModelProperty("应用提示词")
private String prompt;
}

@ -0,0 +1,18 @@
package cn.teammodel.model.dto.ai;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author winter
* @create 2023-12-19 15:42
*/
@Data
public class UpdateSessionDto {
@ApiModelProperty(value = "session id", required = true)
@NotBlank
private String id;
private String title;
}

@ -0,0 +1,27 @@
package cn.teammodel.model.dto.news;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class CreateNewsDto {
@ApiModelProperty("新闻标题")
@NotBlank(message = "新闻标题不能为空")
private String title;
@ApiModelProperty("学段ID")
@NotBlank(message = "学段ID不能为空")
private String periodId;
@ApiModelProperty("新闻描述")
private String desc;
@ApiModelProperty("新闻正文")
@NotBlank(message = "新闻正文不能为空")
private String content;
@ApiModelProperty("新闻封面")
private String cover;
}

@ -0,0 +1,30 @@
package cn.teammodel.model.dto.news;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class UpdateNewsDto {
@ApiModelProperty("新闻 id")
private String id;
@ApiModelProperty("新闻标题")
@NotBlank(message = "新闻标题不能为空")
private String title;
@ApiModelProperty("学段ID")
@NotBlank(message = "学段ID不能为空")
private String periodId;
@ApiModelProperty("新闻描述")
private String desc;
@ApiModelProperty("新闻正文")
@NotBlank(message = "新闻正文不能为空")
private String content;
@ApiModelProperty("新闻封面")
private String cover;
}

@ -0,0 +1,17 @@
package cn.teammodel.model.dto.weekDuty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2024-01-03 10:56
*/
@Data
public class DeleteDutyNodeDto {
@ApiModelProperty("要删除的节点 id")
@NotNull
private String nodeId;
}

@ -0,0 +1,29 @@
package cn.teammodel.model.dto.weekDuty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* @author winter
* @create 2024-01-03 15:05
*/
@Data
public class DutyVoteDto {
@NotNull
@ApiModelProperty(value = "班级id", required = true)
private String classId;
// 拍照巡检暂时不用地点 id
@NotNull
@ApiModelProperty(value = "评价地点 id")
private String spotId;
@NotNull
@ApiModelProperty(value = "评价节点id", required = true)
private String dutyNodeId;
@ApiModelProperty(value = "评分是否传播到学生")
private Boolean spread = false;
private String note;
private List<String> attachments;
}

@ -0,0 +1,25 @@
package cn.teammodel.model.dto.weekDuty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2024-01-08 15:31
*/
@Data
public class FindDutyRecordDto {
@ApiModelProperty(value = "班级 id", notes = "若不携带默认不指定某个班级")
private String classId;
@ApiModelProperty(required = true)
@NotNull(message = "学段 id 不能为空")
private String periodId;
@ApiModelProperty(value = "是否加分", notes = "若不携带默认为全部")
private Boolean positive;
@ApiModelProperty(value = "起始时间", notes = "若不携带默认为当前周")
private Long startTime;
@ApiModelProperty(value = "结束时间", notes = "若不携带默认为当前周")
private Long endTime;
}

@ -0,0 +1,26 @@
package cn.teammodel.model.dto.weekDuty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.PositiveOrZero;
/**
* @author winter
* @create 2024-01-03 10:45
*/
@Data
public class InsertDutyNodeDto {
@ApiModelProperty(value = "父亲节点,不传则为根节点")
String pid;
@ApiModelProperty(value = "节点名称", required = true)
@NotBlank(message = "name 不能为空")
String name;
String desc;
Integer order = 0;
@PositiveOrZero(message = "score 不能为负数")
Integer score = 0;
Boolean positive = true;
}

@ -0,0 +1,18 @@
package cn.teammodel.model.dto.weekDuty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author winter
* @create 2024-01-03 10:45
*/
@Data
public class InsertSpotDto {
@ApiModelProperty(value = "地点名称", required = true)
@NotBlank(message = "name 不能为空")
private String name;
}

@ -0,0 +1,40 @@
package cn.teammodel.model.dto.weekDuty;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@Data
public class LessonRecordDto {
private String tmdid;
@NotNull
private String scope;
@NotNull
private String school;
private String name;
@NotNull
private String periodId;
private Long stime;
private Long etime;
private List<String> category;
private List<String> subjectId;
private List<String> grade;
@Value("${lessonRecordDto.doubleGreen:false}")
private boolean doubleGreen ;
@Value("${lessonRecordDto.singleGreen:false}")
private boolean singleGreen ;
@Value("${lessonRecordDto.isOk:true}")
private boolean isOk ;
@Value("${lessonRecordDto.quality:false}")
private boolean quality;
@Value("${lessonRecordDto.desc:startTime}")
private String desc;
private int pageCount;
private String continuationToken;
@Value("${lessonRecordDto.managePage:true}")
private boolean managePage;
private boolean classMeeting;
}

@ -0,0 +1,20 @@
package cn.teammodel.model.dto.weekDuty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author winter
* @create 2023-11-22 16:16
*/
@Data
public class RecallDutyVoteDto {
@NotNull
@ApiModelProperty(value = "值周记录的文档id", required = true)
String recordId;
@NotNull
@ApiModelProperty(value = "值周记录的具体节点id", required = true)
String nodeId;
}

@ -0,0 +1,26 @@
package cn.teammodel.model.dto.weekDuty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
/**
* @author winter
* @create 2024-01-03 10:59
*/
@Data
@ApiModel("覆盖原节点")
public class UpdateDutyNodeDto {
@ApiModelProperty(value = "待修改的节点 id")
@NotNull
private String id;
private String name;
private String desc;
private Boolean positive = true;
private Integer order = 0;
@PositiveOrZero(message = "分数不能为负数")
private Integer score = 0;
}

@ -0,0 +1,24 @@
package cn.teammodel.model.entity;
import com.azure.spring.data.cosmos.core.mapping.GeneratedValue;
import com.azure.spring.data.cosmos.core.mapping.PartitionKey;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import org.springframework.data.annotation.Id;
/**
* @author winter
* @create 2023-11-27 11:05
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BaseItem {
@Id
@GeneratedValue
private String id;
/**
*
*/
@PartitionKey
private String code;
}

@ -0,0 +1,54 @@
package cn.teammodel.model.entity;
import io.jsonwebtoken.Claims;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* @author winter
* @create 2023-11-09 15:28
*/
@Data
public class TmdUserDetail implements UserDetails {
private User user;
private Claims claims;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return true;
}
}

@ -0,0 +1,34 @@
package cn.teammodel.model.entity;
import lombok.Data;
import lombok.ToString;
import java.util.Set;
/**
* @author winter
* @create 2023-11-09 15:43
*/
@Data
@ToString
public class User {
private String id;
private String name;
/**
* id
*/
private String schoolId;
private String picture;
private String standard;
/**
*
*/
private String scope;
private String website;
/**
* id
*/
private String area;
private Set<String> roles;
private Set<String> permissions;
}

@ -0,0 +1,55 @@
package cn.teammodel.model.entity.ai;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* ()
* code: ChatApp
* @author winter
* @create 2024-01-23 11:08
*/
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "School")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatApp extends BaseItem {
/**
* id (telnet id)
*/
private String schoolId;
/**
*
*/
private String icon;
private String name;
private String bizType;
private String scope;
/**
*
*/
private String lang;
/**
*
*/
private String itemType;
/**
*
*/
private String period;
/**
*
*/
private String subject;
private String description;
/**
*
*/
private String prompt;
private String creator;
private String creatorId;
private Long createTime;
}

@ -0,0 +1,58 @@
package cn.teammodel.model.entity.ai;
import cn.hutool.core.lang.UUID;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.*;
import java.time.Instant;
import java.util.List;
/**
* , teacherId(userId), id: sessionId
* @author winter
* @create 2023-12-19 15:09
*/
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "Teacher")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatSession extends BaseItem {
/**
*
*/
private String title;
/**
* id
*/
private String userId;
private Long createTime;
/**
* ,
*/
private Long updateTime;
private List<Message> history;
@Data
public static class Message {
private String id;
private String userText;
private String gptText;
/**
* point
*/
private Integer cost;
private Long createTime;
public static Message of(String userText, String gptText) {
Message message = new Message();
message.setId(UUID.randomUUID().toString());
message.setCost(0);
message.setUserText(userText);
message.setGptText(gptText);
message.setCreateTime(Instant.now().toEpochMilli());
return message;
}
}
}

@ -0,0 +1,27 @@
package cn.teammodel.model.entity.appraise;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
*
* @author winter
* @create 2023-12-13 15:23
*/
@Data
public class AchievementRule {
private String id;
@ApiModelProperty("等级名称")
private String name;
@ApiModelProperty("等级 logo")
private String logo;
@ApiModelProperty("等级 logo")
private Integer level;
@ApiModelProperty("等级顺序")
private Integer levelCount;
@ApiModelProperty("晋级所需下一等级所需当前等级次数")
private Integer promotionLevel;
@ApiModelProperty("晋升到下一等级所需总表扬数")
private Integer promotionCount;
}

@ -0,0 +1,37 @@
package cn.teammodel.model.entity.appraise;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
*
* @author winter
* @create 2023-11-20 11:04
*/
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "School")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Appraise extends BaseItem {
/**
* Id
*/
private String schoolId;
/**
* id ( default, template)
*/
private String periodId;
/**
*
*/
private List<AppraiseTreeNode> nodes;
/**
*
*/
private List<AchievementRule> achievementRules;
}

@ -0,0 +1,59 @@
package cn.teammodel.model.entity.appraise;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* (+ = ) <br/>
* : : academicYearId(-{semesterId}) + studentId + code(AppraiseRecord-{schoolId})
* @author winter
* @create 2023-11-27 11:02
*/
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "Student")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AppraiseRecord extends BaseItem {
/**
* id
*/
private String periodId;
/**
* id
*/
private String classId;
private String className;
/**
* ( id: -semesterId -> 2023-{semesterId})
*/
private String academicYearId;
/**
* /class id
*/
private String targetId;
private String targetType;
/**
* /class
*/
private String name;
/**
* /class
*/
private String avatar;
private String gender;
/**
*
*/
private Integer praiseCount;
/**
*
*/
private Integer score;
private List<AppraiseRecordItem> nodes;
}

@ -0,0 +1,25 @@
package cn.teammodel.model.entity.appraise;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/**
*
* @author winter
* @create 2023-11-27 16:47
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AppraiseRecordItem {
private String id;
private AppraiseTreeNode appraiseNode;
/**
* , null
*/
private Boolean spread;
private Boolean pushParent;
String creator;
String creatorId;
private Long createTime;
}

@ -0,0 +1,35 @@
package cn.teammodel.model.entity.appraise;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AppraiseTreeNode {
String id;
String pid;
String name;
String[] path;
String logo;
String creator;
String creatorId;
Long createTime;
/**
*
*/
Integer order;
/**
*
*/
Integer score;
/**
* ? true
*/
@JsonProperty("isPraise")
boolean isPraise;
List<AppraiseTreeNode> children;
}

@ -0,0 +1,91 @@
package cn.teammodel.model.entity.common;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "Common")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Art extends BaseItem {
private String name;
private String school;
public List<Tasks> settings;
public String creatorId;
public String createTime;
public String updateTime;
public int type;
public List<String> classes;
public List<String> stuLists;
public List<String> tchLists;
public List<String> sIds;
public String progress;
public String scope;
public int status;
public long size;
public String owner;
public String areaId;
public String pId;
public String presenter;
public String topic;
public long startTime;
public long endTime;
public long uploadSTime;
public long uploadETime;
public String uploadProgress;
public String address;
public String pk;
public String desc;
public String img;
public int publish;
public List<ArtSubject> subjects;
public ArtPeriod period;
public String periodType;
public List<LostStudent> lost;
public int pass;
public List<Integer> miss;
public List<Zymusicstd> zymusicstds;
public static class Acs {
public String infoId ;
public String acId ;
public String name ;
public String subject ;
public int isOrder ;
public int type ;
public String workDesc ;
public long workEnd ;
}
public static class Tasks {
public String id ;
public String quotaname ;
public List<String> path ;
public List<Acs> task ;
}
public static class ArtPeriod
{
public String id ;
public String name ;
}
public static class ArtSubject {
public String id ;
public String name ;
}
public static class LostStudent {
public String code ;
public String subject ;
public double stu ;
}
private static class Zymusicstd
{
public String code ;
public String label ;
public double percent ;
}
}

@ -0,0 +1,27 @@
package cn.teammodel.model.entity.news;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* code: News-{schoolId}
* @author winter
* @create 2024-02-26 17:05
*/
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "School")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class News extends BaseItem {
private String title;
private String periodId;
private String desc;
private String content;
private String cover;
private String creator;
private String creatorId;
private Long createTime;
}

@ -0,0 +1,31 @@
package cn.teammodel.model.entity.school;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "School")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ClassInfo extends BaseItem {
private String no;
private String name;
private Teacher teacher;
private String periodId;
private Integer year;
private String room;
private String school;
private Integer graduate;
private Integer grade;
private String pk;
private Integer ttl;
@Data
public static class Teacher {
private String id;
private String name;
}
}

@ -0,0 +1,139 @@
package cn.teammodel.model.entity.school;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "School")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class School extends BaseItem {
/**
*
*/
private String name;
private String schoolCode;
private String region;
private String province;
private String city;
private String dist;
private String areaId;
private Integer size;
private Integer tsize;
private String address;
private String picture;
private TimeZone timeZone;
private Integer type;
private String standard;
private Integer hpappraise;
private Integer scale;
private Edition edition;
private long createTime;
private boolean isinit;
private boolean openLessonRecord;
//private Module[] modules;
private String pk;
private List<Period> period;
private List<Campus> campuses;
@Data
public static class Period {
private List<Major> majors;
private List<String> grades;
private List<Subject> subjects;
private List<Semester> semesters;
private List<Semester> timetable;
private String name;
private String id;
private String gradeCount;
private String semesterCount;
private String subjectCount;
private String campusId;
private Analysis analysis;
// 学段类型
private String periodType;
}
@Data
public static class Major {
private String id;
private String name;
}
@Data
public static class Timetable {
private String id;
private String label;
private String time;
// todo: 待补充
//private String weeklies;
}
@Data
public static class Analysis {
private List<Type> type;
private Integer income;
private Integer eugenics;
private Integer touch;
}
@Data
public static class Type {
private String id;
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Subject {
private String id;
private String name;
private Integer type;
private String bindId;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Semester {
private String id;
private String name;
/**
* start = 1
*/
private Integer start;
/**
*
*/
private Integer month;
/**
*
*/
private Integer day;
}
@Data
public static class Campus {
private String name;
private String id;
}
@Data
public static class TimeZone {
private String label;
private String value;
}
@Data
public static class Edition {
private Integer current;
private Integer record;
private String scaleVersion;
}
}

@ -0,0 +1,16 @@
package cn.teammodel.model.entity.school;
import lombok.Data;
/**
*
* @author winter
* @create 2023-12-13 11:44
*/
@Data
public class SchoolConfig {
/**
*
*/
private String scoreName = "醍摩豆";
}

@ -0,0 +1,49 @@
package cn.teammodel.model.entity.school;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.azure.spring.data.cosmos.core.mapping.PartitionKey;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import org.springframework.data.annotation.Id;
import java.util.List;
@Data
@Container(containerName = "Student")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Student {
@Id
private String id;
@PartitionKey
private String code;
private String mail;
private String mobile;
private String country;
private String name;
private String picture;
private String schoolId;
private String pw;
private String salt;
private Integer year;
private String no;
private String irs;
private String classId;
private String groupId;
private String groupName;
private String periodId;
private String gender;
private Integer graduate;
private List<LoginInfo> loginInfos;
private Long createTime;
//private List<String> guardians;
private String pk;
@Data
public static class LoginInfo {
private Long time;
private String ip;
private Long expire;
}
}

@ -0,0 +1,53 @@
package cn.teammodel.model.entity.school;
import cn.teammodel.model.entity.BaseItem;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Container(containerName = "Teacher")
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Teacher extends BaseItem {
private String name;
private String picture;
private int size;
private String defaultSchool;
private List<School> schools;
private List<Area> areas;
private List<LoginInfo> loginInfos;
private long createTime;
//private List<String> binds;
private int lessonLimit;
//private List<String> lessonShow;
private String lang;
private String pk;
@Data
public static class School {
private String schoolId;
private String name;
private String status;
private long time;
private String picture;
private String areaId;
}
@Data
public static class Area {
private String areaId;
private String name;
private String status;
}
@Data
public static class LoginInfo {
private long time;
private String ip;
private long expire;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save