Giter Club home page Giter Club logo

dingtalk-app-server's Introduction

logo

Dingtalk App Server

CodeFactor

目标与期望

基于钉钉微应用开发的实验室绩效管理系统,将实验室的绩效、学分、论文评审管理与钉钉对接。
主要功能有:绩效、学分申请与审核,论文评审投票及学分管理,实验室助研金计算等,导出绩效和助研金报表。

开发环境

SpringBoot

JPA

Mybatis

MySQL 8

Dingtalk SDK

Docker

Docker Compose

Github Actions

注意事项

  • 使用了lombok 插件简化代码,idea 需要安装lombok 插件,否则编译过不去
  • 系统启动时,初始化操作会调用钉钉SDK,拉取钉钉组织的所有用户, 请先在开发平台设置出口IP

持续部署

本项目使用 GitHub Actions 实现 CI,受外网网速限制,没有采用在 GitHub 机器上构件镜像,再拉取到服务器上运行的方式。而是在每次 CI 触发后,GitHub 机器 ssh 登陆服务器,执行脚本来拉取最新代码,构建镜像,并运行容器,具体如下:

  1. 从GitHub仓库中拉去最新代码到服务器本地仓库
  2. 使用mvn构建项目
  3. docker-compose build 构建镜像
  4. docker-compose up -d 在后台启动容器
  5. docker image prune -f 清理无用的镜像

部署相关脚本如下

系统运维

前端预览

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19

project-detail-1.png project-detail-2.png

dingtalk-app-server's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

dingtalk-app-server's Issues

反序列化时一对多关系循环引用与直接将对象的属性(数组)序列化为List<?>冲突

为了避免序列化Application时出现循环引用,在application类的acItems属性上加上了@JsonIgnore
结果 ApplicationControlleraddApplication方法出现了空指针异常
发现是因为@JsonIgnore导致反序列化时acItems被忽略了,导致acItems为空

#6 的第一个解决方案有问题,还是需要将applicationacItems分开来传给后台,但是可以封装起来

bug: whether the exception information of repeated voting can be encapsulated into user-friendly info

nju-softeng/dingtalk-app-web#29

缺陷描述:

进入投票页面后,第一次投票,页面没有变化;第二次投票出现异常,但前端异常信息为null, (这里其实是后端 Duplicate entry 'v_id-u_id' for key, 重复投票的异常)

问题分析:

投票后确实发送到后端了,但是前端没有正确切换到投票状态页面,后端异常信息也没提示 ”用户已投票“

  • 前端异常log:
POST http://47.104.103.33:8089/api/vote/52 500 ()
{timestamp: "2021-12-03", status: 500, error: "Internal Server Error", message: "", path: "/api/vote/52"}
  • 后端异常log:
Duplicate entry '52-27' for key 'vote_detail.UK61vpx1woxr2v6exm8a2hxwtgb'
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [vote_detail.UK61vpx1woxr2v6exm8a2hxwtgb]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '52-27' for key 'vote_detail.UK61vpx1woxr2v6exm8a2hxwtgb'
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1092)
........

Bug(service.VoteService并发问题)

可能出现并发问题

public Vote createVote(VoteVO voteVO) {
// 判断投票是否已经创建过
if (voteRepository.isExisted(voteVO.getPaperid(), false) != 0) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "慢了一步,投票已经被别人发起了");
}
Vote vote = new Vote(LocalDateTime.now() ,LocalDateTime.of(LocalDate.now(), voteVO.getEndTime()), voteVO.getPaperid());
voteRepository.save(vote);

更新ac_record的sql语句

select ac_record_id FROM paper_detail WHERE paper_id = 6

select * FROM ac_record ac RIGHT JOIN (select ac_record_id FROM paper_detail WHERE paper_id = 6) t on ac.id = t.ac_record_id

update ac_record a, (select ac_record_id FROM paper_detail WHERE paper_id = 6) t set a.create_time = '2021-01-15' WHERE a.id = t.ac_record_id

提醒功能

对学生不利的情况将要出现(扣分……),提前提醒学生规避

  • 投票
  • 绩效填写
  • 周报
  • 通知审核人审核

refactor(VoteService)

注意到用户投一张票,调用一次poll函数,只会在vote_detail表中添加一条记录,不会产生ac_record,这段代码没有必要。

// 删除旧的 acRecord
List<AcRecord> oldAcRecords = Optional.ofNullable(voteDetails).orElse(new ArrayList<>()).stream()
.filter(x -> x.getAcRecord() != null)
.map(x -> x.getAcRecord())
.collect(Collectors.toList());
acRecordRepository.deleteAll(oldAcRecords);

Team meeting sign-in solution

  • 组会签到可以通过定位 + 扫二维码的形式实现?
  • 钉钉现有的签到接口是否可以使用?定位精度问题?
  • 这个需求是否太严格,是否增加同学负担,如何销假》

MySQL生成大量测试数据方法

使用存储过程link

实践:
1.创建测试数据库

 create database test charset=utf8;
 use test;

2.创建数据表

CREATE TABLE `dc_record` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dc` double DEFAULT NULL,
  `insert_time` datetime NOT NULL,
  `week` int(11) DEFAULT NULL,
  `application_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `auditor_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=166104 DEFAULT CHARSET=utf8;

3.创建存储过程

CREATE DEFINER=`root`@`localhost` PROCEDURE `insertdata`(IN n int)
BEGIN  
  DECLARE i INT DEFAULT 1;
	DECLARE j INT;
  WHILE (i <= n ) DO
		WHILE (j <= 100) DO
			INSERT into dc_record (application_id, auditor_id, user_id,insert_time, week, dc) VALUES (i, i, j ,now(), 1, FLOOR(RAND() * 100) );
			SET j=j+1;
		END WHILE;
		SET i=i+1;
		SET j = 1;
  END WHILE;
END

绩效报表完善

  • 将每月的AC变动集成到每月的绩效表中
  • 增加专硕科研津贴项:满足某个条件(当月DC > ?)获得 x 元津贴

refactor(string append): can concatenate strings using string placeholders

private String voteResultInfo(String title, boolean result, int acceptCnt, int totalCnt) {
return new StringBuilder().append(" #### 投票结果 \n ##### 论文: ").append(title)
.append(" \n 最终结果: ").append(result ? "Accept" : "reject")
.append(" \n Accept: ").append(acceptCnt).append(" 票 \n ")
.append("Reject: ").append(totalCnt - acceptCnt).append(" 票 \n ")
.append("已参与人数: ").append(totalCnt).append("人 \n ").toString();
}

是否需要用 String.format 代替 append(), 性能和可读性的权衡?
https://cowtowncoder.medium.com/measuring-performance-of-java-string-format-or-lack-thereof-2e1c6a13362c

更新预测原因ac 的sql

select ac_record_id, vote_id FROM vote_detail WHERE ac_record_id is not null


SELECT id from vote WHERE external = 0

SELECT v.id, p.title from paper p LEFT JOIN vote v on p.vote_id = v.id 


SELECT * FROM (select ac_record_id, vote_id FROM vote_detail WHERE ac_record_id is not null
) A INNER JOIN (SELECT v.id, p.title from paper p LEFT JOIN vote v on p.vote_id = v.id ) B on A.vote_id = B.id


UPDATE ac_record a, (SELECT * FROM (select ac_record_id, vote_id FROM vote_detail WHERE ac_record_id is not null
) A INNER JOIN (SELECT v.id, p.title from paper p LEFT JOIN vote v on p.vote_id = v.id ) B on A.vote_id = B.id) tmp set a.reason = CONCAT(tmp.title, a.reason) WHERE a.id = tmp.ac_record_id

SELECT v.id, p.title from external_paper p LEFT JOIN vote v on p.vote_id = v.id 


SELECT * FROM (select ac_record_id, vote_id FROM vote_detail WHERE ac_record_id is not null
) A INNER JOIN (SELECT v.id, p.title from external_paper p LEFT JOIN vote v on p.vote_id = v.id ) B on A.vote_id = B.id


UPDATE ac_record a, (SELECT * FROM (select ac_record_id, vote_id FROM vote_detail WHERE ac_record_id is not null
) A INNER JOIN (SELECT v.id, p.title from external_paper p LEFT JOIN vote v on p.vote_id = v.id ) B on A.vote_id = B.id) tmp set a.reason = CONCAT(tmp.title, a.reason) WHERE a.id = tmp.ac_record_id

java.lang.NoClassDefFoundError(第三方依赖jar包的问题)

maven项目通过在pom添加依赖导入本地jar包。项目部署打成 jar包后,运行时会出现java.lang.NoClassDefFoundError, 并且解压jar包发现BOOT-INF/lib下没有之前引入jar包

 <!--添加外部依赖-->
<dependency>
    <groupId>com.dingtalk.open</groupId>
    <artifactId>taobao-sdk-java-auto</artifactId>
    <version>2019.12.05</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/main/resources/lib/taobao-sdk-java-auto.jar</systemPath>
</dependency>

解决方案
我们需要在pom中给springboot的打包插件设置includeSystemScope参数

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                </configuration>
            </plugin>
        </plugins>
</build>

重点在于

<configuration>
    <includeSystemScope>true</includeSystemScope>
</configuration>

SQL 提取AC数据

SELECT
	u.NAME AS NAME,
	a.ac,
	a.reason, 
	create_time
FROM
	( SELECT * FROM `user` WHERE is_deleted = 0 ) u
	INNER JOIN ( SELECT * FROM ac_record WHERE create_time > "2020-12-31" and create_time < "2021-02-01") a ON a.user_id = u.id 
ORDER BY
	user_id DESC

refactor(DcRecord): Delete the fields of the table and reconstruct the corresponding code

DcRecord的表字段:id、dvalue、cvalue、dc、ac、status、insert_time、yearmonth、week、weekdate、version、datecode、applicant_id、auditor_id、(ac_items)
重构为:id、dvalue、cvalue、status、insert_time、year_month_week、version、applicant_id、auditor_id
说明:

  1. 删除dc:dc可以在dao层用select dvalue * cvalue as dc ……算出来
  2. 删除ac和ac_items:这功能单独提出来
  3. yearmonth、week、weekdate、datecode由year_month_week替代:yyyyMMww这样的整数应该可以替代这些字段的功能

关于该项目中方法命名的建议

尊敬的开发者:
您好!非常感谢您能抽出宝贵的时间来阅读此Issue,我们是来自西北工业大学软件学院硕士课题组的科研团队,正在进行一项关于Java开源项目中方法(函数)名称一致性检查和建议的科研研究,方法(函数)名称的可读性对开发人员理解代码至关重要,我们在本开源项目中随机选择了一些文件作为我们研发工具DMName的实验验证对象,共发现了以下存在的15个方法(函数)命名问题,原始的建议修改的方法名称是第3列original_name,建议的方法名称为第4列suggest_name::

<style> </style>
path line original_name suggest_name
dingtalk-app-server/src/test/java/com/softeng/dingtalk/service/ApplicationServiceTests.java 93 testSetByAuditor testSetApplicationByAuditor
dingtalk-app-server/src/test/java/com/softeng/dingtalk/service/PaperServiceTest.java 59 testUpdateExternalPaperShouldOk testUpdateExternalPaper
dingtalk-app-server/src/test/java/com/softeng/dingtalk/service/PerformanceServiceTest.java 38 test1 testListDcSummaryData
dingtalk-app-server/src/test/java/com/softeng/dingtalk/DingtalkApplicationTests.java 34 test testEncryptor
dingtalk-app-server/src/test/java/com/softeng/dingtalk/DingtalkApplicationTests.java 39 test2 testAddInternalPaper
dingtalk-app-server/src/main/java/com/softeng/dingtalk/api/BaseApi.java 53 setCORPID setCorpId
dingtalk-app-server/src/main/java/com/softeng/dingtalk/api/BaseApi.java 73 setAGENTID setAgentId
dingtalk-app-server/src/main/java/com/softeng/dingtalk/api/BaseApi.java 78 setDOMAIN setDomain
dingtalk-app-server/src/main/java/com/softeng/dingtalk/api/BaseApi.java 194 sign getSignature
dingtalk-app-server/src/main/java/com/softeng/dingtalk/api/BaseApi.java 214 authentication authSignature
dingtalk-app-server/src/main/java/com/softeng/dingtalk/config/FabricConfig.java 36 FabricConfig configFabric
dingtalk-app-server/src/main/java/com/softeng/dingtalk/entity/AbsentOA.java 45 AbsentOA setAbsentOA
dingtalk-app-server/src/main/java/com/softeng/dingtalk/entity/AcItem.java 45 AcItem setAcItem
dingtalk-app-server/src/main/java/com/softeng/dingtalk/entity/Bug.java 55 Bug setBugId
dingtalk-app-server/src/main/java/com/softeng/dingtalk/entity/BugDetail.java 37 BugDetail setBugDetail

如果您认可或反对上述所涉及的问题和命名建议,可以发邮件([email protected])联系我们或直接在本Issue下回复,我们由衷地希望能够得到您宝贵的意见反馈,期待您的回复!

回收AC 的sql

select * FROM paper_detail WHERE paper_id = 4

select * FROM ac_record ac RIGHT JOIN (select ac_record_id FROM paper_detail WHERE paper_id = 4) t on ac.id = t.ac_record_id

update ac_record a, (select ac_record_id FROM paper_detail WHERE paper_id = 4) t set a.reason = CONCAT('(AC待分配)', a.reason), a.ac = 0  WHERE a.id = t.ac_record_id

bug(dc_record): dc summary problem

问题:计算某周dc时没有排除未审核的dc,即没有加上where status <> 0。没出问题是因为未审核的dc记录c值为0

/**
* 获取 dc_record 的指定用户所在日期,周,所有dc值之和(即包括其他审核人审核的dc值)
* @param uid, yearmonth, week
* @return java.lang.Double
* @Date 8:34 PM 1/2/2020
*
**/
@Query(value = "SELECT IFNULL((select sum(dc) from dc_record where applicant_id = :uid and yearmonth = :yearmonth and week = :week), 0)",
nativeQuery = true)
Double getUserWeekTotalDc(@Param("uid") int uid, @Param("yearmonth") int yearmonth, @Param("week") int week);

后端定义DTO对象

  • 说明:现阶段后端controller层返回值全是Map,而且前端依赖undefined值,即后端返回的Map中没有定义字段,前端依赖了后端这个行为。
  • 要求:后端定义DTO对象,前端修改逻辑

自定义JPA主键生成策略实现保存时允许自定义ID

而在一些场景下,会有自定义主键的需求,比如主键来源于其他第三方系统,这时候我们期望的还是使用第三方系统的主键作为主键以表示同一条数据,这就需要我们能够实现自定义主键生成策略,并且JPA也提供了这样的通道。
参考1

参考2

实体类的属性是一个List (OneToMany关系), 前端如何将数据一次性传给后端

如下,一个Application包含多个AcItem。前端如何一次性将这一对多的关系提交?

public class Application {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private int month;
    private int week;
    private int DC;
    private boolean isCheck;   // 是否已审核
    @Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP", updatable = false, insertable = false)
    private LocalDateTime insertTime;   //插入时间

    @ManyToOne
    private User applicant;    // 申请人
    @ManyToOne
    private User auditor;      // 审核人


    @OneToMany(mappedBy = "application")
    private List<AcItem> acItems;  //本次绩效申请包含的 AC申请
}
public class AcItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private int AC;
    private String reason;

    @ManyToOne
    private Application application;  //ac申请属于的周绩效申请
}

refactor(internal_paper, external_paper): Merge into one table

问题:外部论文和内部论文两张表没多大差别,代码冗余。
方案:合成一张表,加个is_external字段,前端增加相应的是否是外部论文的选项,如果不是,扩展必填字段,如果是,直接提交。

配置内网穿

调用钉钉服务端API需要配置合法IP列表,因为开发环境没有公网IP, 需要进行内网穿透

更新投票获得ac原因的SQL

SELECT
	ac_record_id,
	vote_id,
	result AS v_res 
FROM
	vote_detail 
WHERE
	ac_record_id IS NOT NULL


SELECT
	v.id,
	p.title,
	p.result AS p_res 
FROM
	paper p
	LEFT JOIN vote v ON p.vote_id = v.id


SELECT
	title,
	ac_record_id,
	user_id,
	case
		when B.p_res = 4 and A.v_res = 1 or B.p_res = 3 and A.v_res = 0 then
			1
		else 
			0
	END
	as res
FROM
	( SELECT ac_record_id, vote_id, user_id, result AS v_res FROM vote_detail WHERE ac_record_id IS NOT NULL ) A
	INNER JOIN (
	SELECT
		v.id,
		p.title,
		p.result AS p_res 
	FROM
		paper p
		LEFT JOIN vote v ON p.vote_id = v.id 
	) B ON A.vote_id = B.id


UPDATE ac_record a,
(
	SELECT
		title,
		ac_record_id,
	CASE
			
			WHEN B.p_res = 4 
			AND A.v_res = 1 
			OR B.p_res = 3 
			AND A.v_res = 0 THEN
				1 ELSE 0 
			END AS res 
		FROM
			( SELECT ac_record_id, vote_id, user_id, result AS v_res FROM vote_detail WHERE ac_record_id IS NOT NULL ) A
			INNER JOIN (
			SELECT
				v.id,
				p.title,
				p.result AS p_res 
			FROM
				paper p
				LEFT JOIN vote v ON p.vote_id = v.id 
			) B ON A.vote_id = B.id 
		) tmp 
		SET a.reason = if(res = 1, CONCAT('投票预测正确:' , tmp.title ), CONCAT('投票预测错误:' , tmp.title ))
WHERE
	a.id = tmp.ac_record_id

算法层抽取

将service层中的根据实验室规则算分的逻辑抽取整合成一层,前端做一个单独的管理后台页面,用来动态配置这一层参数(所有硬编码参数改掉,动态配置)。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.