├── .gitignore ├── LICENSE ├── README.md ├── db └── init.sql ├── docs └── demo.gif ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── catframework │ │ └── agileworking │ │ ├── Application.java │ │ ├── BusinessException.java │ │ ├── common │ │ └── ResponseCodes.java │ │ ├── domain │ │ ├── MeetingRoom.java │ │ ├── MeetingRoomRepository.java │ │ ├── Participant.java │ │ ├── ParticipantRepository.java │ │ ├── Schedule.java │ │ ├── ScheduleRepository.java │ │ ├── ScheduleRepositoryCustom.java │ │ ├── ScheduleRepositoryImpl.java │ │ ├── Team.java │ │ ├── TeamRepository.java │ │ ├── User.java │ │ ├── UserRepository.java │ │ └── package-info.java │ │ ├── scheduling │ │ └── SendNotifyMessageJob.java │ │ ├── service │ │ ├── ScheduleService.java │ │ ├── WebTokenService.java │ │ ├── impl │ │ │ ├── ScheduleServiceImpl.java │ │ │ └── SimpleJJWTWebTokenServiceImpl.java │ │ └── package-info.java │ │ ├── utils │ │ ├── DateUtils.java │ │ └── JsonUtils.java │ │ ├── vo │ │ ├── NotifyMessageTemplate.java │ │ └── ScheduleVO.java │ │ └── web │ │ ├── MeetingRoomController.java │ │ ├── ParticipantController.java │ │ ├── ScheduleController.java │ │ ├── TeamController.java │ │ ├── WechatController.java │ │ ├── package-info.java │ │ └── support │ │ ├── DefaultResult.java │ │ ├── GlobalExceptionHandler.java │ │ ├── Result.java │ │ └── WebTokenHandlerInterceptor.java └── resources │ ├── application-dev.properties │ ├── application-prd.properties │ ├── application.properties │ └── logback.xml └── test ├── java └── org │ └── catframework │ └── agileworking │ ├── domain │ ├── MeetingRoomFactory.java │ ├── ScheduleFactory.java │ ├── ScheduleRepositoryTest.java │ ├── ScheduleTest.java │ ├── TeamFactory.java │ └── UserFactory.java │ ├── package-info.java │ ├── scheduling │ └── SendNotifyMessageJobTest.java │ ├── service │ └── impl │ │ ├── ScheduleServiceImplTest.java │ │ ├── SimpleJJWTWebTokenServiceImplTest.java │ │ └── WeChatApiIntegrationTest.java │ ├── utils │ ├── DateUtilsTest.java │ └── JsonUtilsTest.java │ ├── vo │ └── NotifyMessageTemplateTest.java │ └── web │ ├── MeetingRoomControllerIntegrationTest.java │ ├── MeetingRoomControllerTest.java │ ├── ScheduleControllerTest.java │ ├── TeamControllerTest.java │ └── WechatControllerIntegrationTest.java └── resources └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.classpath 3 | /.project 4 | /.idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 7upcat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # agile-wroking-backend 2 | 3 | 此项目是 *real world* 小程序 *AgileWorking* 应用的后端,*AgileWorking* 是一个用于部门内部会议室及团队管理的小程序,小程序的前端实现 可以访问此项目 [agile-working](https://github.com/wufajin/agile-working-1.0.0)。 4 | 5 | ## 特色 6 | 7 | - 全面使用 *Java8* 函数式编码风格,代码简洁、高效,易读 8 | - 采用 [领域模型设计](https://en.wikipedia.org/wiki/Domain-driven_design) 的风格组织代码,应对复杂逻辑 9 | - 使用 *SpringBoot* 构建,*Convension Over Configuration*,零配置 10 | - 提供 *Restful* 风格的 API 易于前端访问 11 | - 支持小程序的会话管理及接口调用的安全控制 12 | - 支持会议提醒及邀请其他人加入会议 13 | 14 | ## 演示 15 | 16 | ![](docs/demo.gif) 17 | 18 | ## 构建 19 | 20 | ### 前置条件 21 | 22 | - Jdk1.8+ 23 | - 安装配置 [Maven](http://maven.apache.org/install.html) 24 | - 安装 [Git](https://git-scm.com/downloads) 25 | 26 | ### 构建步骤 27 | 28 | - git clone https://github.com/7upcat/agile-wroking-backend.git 29 | - cd agile-working-backend 30 | - mvn package 31 | - java -jar target\agile-working-backend-1.0.3.jar 32 | 33 | ## 单元测试 34 | 35 | - 进行单元测试前在 *src/main/resources/application.properties* 中设置 profile `spring.profiles.active=dev`,将会自动连接测试的数据库,并在每个 36 | 案例执行前重新建表 37 | - 进行单元测试 `mvn test` 38 | 39 | ## 接口清单 40 | 41 | 接口设计遵循 *Restful* 风格的 **API**,**公开** 标签下的服务可以直接访问,**私有** 标签的服务必须在 http header 中指定 `Authorization`(Token) 及 `Subject`(微信 openId): 42 | 43 | - 【公开】通过 `jsCode` 获取用户的 `openId` `/agileworking/wechat/openid/{jsCode}`,成功查询 payload 中返回的即是 openId 44 | 45 | - 【公开】查询团队列表,返回所有的团队 `/agileworking/teams` 46 | 47 | - 【公开】查询指定 `openId` 的用户是否有加入指定的团队 **GET** `/agileworking/team/{teamId}/user/{openId}`,如果加入则返回 `User` 信息及 `token` 48 | 49 | - 【公开】加入指定的团队 **POST** `/agileworking/team/{id}/join` ,加入成功会返回 `User` 及 `token` 50 | 51 | + name/姓名 52 | + mobileNo/手机号 53 | + openId/微信 openId 54 | + nickName/微信昵称 55 | + avatarUrl/微信头像 url 56 | + token/团队的加入口令 57 | 58 | - 【公开】根据 `id` 查询指定的排期 **GET** `/agileworking/schedules/{id}`,含排期的参与人,此接口用于邀请其他用户打开小程序时使用,暂时放开为公共,后续待前端优化后修改回私有 59 | 60 | - 【私有】查询指定团队下的所有会议室列表 **GET** `/agileworking/meetingRooms/{teamId}` 61 | 62 | - 【私有】创建/修改排期 **POST** `/agileworking/meetingRooms/{id}/schedule?formId=?` 63 | + id/排期id(可选,创建排期为空) 64 | + title/标题 65 | + date/日期 `yyyy-MM-dd` 格式 66 | + startTime/开始时间(hh:min) 67 | + endTime/结束时间(hh:min) 68 | + creatorOpenId/创建人微信 openId 69 | + creatorNickName/创建人微信昵称 70 | + creatorAvatarUrl/创建人微信头像URL 71 | + repeatMode/会议重复模式(N-不重复/W-每周) 72 | 73 | - 【私有】取消排期 **DELETE** `/agileworking/meetingRooms/schedule/{id}` 74 | 75 | - 【私有】查询指定会议室指定日期的排期 **GET** `/agileworking/meetingRooms/{id}/schedule?date=yyyyMMdd` 76 | 77 | - 【私有】接受会议邀请 **POST** `/agileworking/schedules/{id}/join` 78 | 79 | + openId/接受邀请人微信 openId 80 | + nickName/接受邀请人微信昵称 81 | + avatarUrl/接受邀请人微信头像URL 82 | + formId/表单 id ,用于后续的微信消息通知 83 | 84 | - 【私有】查询加入的会议 **GET** `/agileworking/participant/{openId}?date=yyyyMMdd` 85 | 86 | + scheduleId/排期id 87 | + meetingRoomId/会议室 id 88 | + date/排期的日期 89 | + title/会议主题 90 | + openId/参会人的微信 openId 91 | + roomNo/会议室 92 | + startTime/开始时间 93 | + endTime/结束时间 94 | + repeatMode/会议重复模式(N-不重复/W-每周) 95 | -------------------------------------------------------------------------------- /db/init.sql: -------------------------------------------------------------------------------- 1 | -- 组别初始化数据 2 | INSERT INTO team(id,name,team_desc,token) VALUES (1,'深研二部','一个敏捷的团队~','123456'); 3 | 4 | -- 会议室初始化数据 5 | INSERT INTO meeting_room (ip, room_no, size, terminal_id, type, vnedor,team_id) VALUES ('182.207.96.163', '3201', '大', '21169', '视频', '思科',1); 6 | INSERT INTO meeting_room (ip, room_no, size, terminal_id, type, vnedor,team_id) VALUES ('182.207.96.166', '3202', '中', '120706', '视频', '华为',1); 7 | INSERT INTO meeting_room (ip, room_no, size, terminal_id, type, vnedor,team_id) VALUES (null, '3203', '小', null, '普通', null,1); 8 | INSERT INTO meeting_room (ip, room_no, size, terminal_id, type, vnedor,team_id) VALUES (null, '3207', '小', null, '普通', null,1); 9 | INSERT INTO meeting_room (ip, room_no, size, terminal_id, type, vnedor,team_id) VALUES (null, '3403', '小', null, '普通', null,1); 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7upcat/agile-wroking-backend/3753b8226b28a77655b4746dc7f1d61be61a7969/docs/demo.gif -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.catframework 5 | agile-working-backend 6 | 1.0.3 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 1.5.5.RELEASE 11 | 12 | 13 | 1.8 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-web 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-data-jpa 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-websocket 27 | 28 | 29 | org.apache.tomcat 30 | tomcat-jdbc 31 | 32 | 33 | mysql 34 | mysql-connector-java 35 | 36 | 37 | io.jsonwebtoken 38 | jjwt 39 | 0.7.0 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-maven-plugin 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-surefire-plugin 56 | 57 | 58 | **/*IntegrationTest.java 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/Application.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking; 2 | 3 | import javax.sql.DataSource; 4 | 5 | import org.apache.tomcat.jdbc.pool.PoolProperties; 6 | import org.catframework.agileworking.web.support.WebTokenHandlerInterceptor; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.scheduling.annotation.EnableScheduling; 12 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 14 | 15 | @SpringBootApplication 16 | @EnableScheduling 17 | public class Application extends WebMvcConfigurerAdapter { 18 | 19 | @Bean 20 | public DataSource dataSource() { 21 | return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties()); 22 | } 23 | 24 | @Bean 25 | @ConfigurationProperties(prefix = "spring.datasource") 26 | public PoolProperties poolProperties() { 27 | return new PoolProperties(); 28 | } 29 | 30 | @Bean 31 | WebTokenHandlerInterceptor webTokenHandlerInterceptor() { 32 | return new WebTokenHandlerInterceptor(); 33 | } 34 | 35 | @Override 36 | public void addInterceptors(InterceptorRegistry registry) { 37 | registry.addInterceptor(webTokenHandlerInterceptor()).addPathPatterns("/**"); 38 | } 39 | 40 | public static void main(String[] args) throws Exception { 41 | SpringApplication.run(Application.class, args); 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/BusinessException.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking; 2 | 3 | public class BusinessException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 3999713779564898790L; 6 | 7 | private String code; 8 | 9 | public BusinessException(String code) { 10 | this.code = code; 11 | } 12 | 13 | public BusinessException(String code, String message) { 14 | super(message); 15 | this.code = code; 16 | } 17 | 18 | public BusinessException(String code, String message, Throwable cause) { 19 | super(message, cause); 20 | this.code = code; 21 | } 22 | 23 | public String getCode() { 24 | return code; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/common/ResponseCodes.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.common; 2 | 3 | /** 4 | * 集中的定义系统中的响应码,格式如下: 5 | * 9 | * 10 | * @author devzzm 11 | * 12 | */ 13 | public final class ResponseCodes { 14 | 15 | /** 响应码:成功. */ 16 | public static final String RESPONSE_CODE_SUCCESS = "SC0000"; 17 | 18 | /** 响应码:默认的系统处理异常,用于非预期的运行时异常. */ 19 | public static final String RESPONSE_CODE_SYSTEM_ERROR = "ER0001"; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/MeetingRoom.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.io.Serializable; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.Id; 9 | 10 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 11 | 12 | /** 13 | * 会议室实体. 14 | * 15 | * @author devzzm 16 | */ 17 | @Entity 18 | @JsonIgnoreProperties({ "handler", "hibernateLazyInitializer" }) 19 | public class MeetingRoom implements Serializable { 20 | 21 | private static final long serialVersionUID = -1701470269000866582L; 22 | 23 | @Id 24 | @GeneratedValue 25 | private Long id; 26 | 27 | @Column(nullable = false) 28 | private Long teamId; 29 | 30 | /** 会议室房间编号,唯一. */ 31 | @Column(nullable = false, unique = true) 32 | private String roomNo; 33 | 34 | /** 会议室大小: 大/中/小. */ 35 | @Column(nullable = false) 36 | private String size; 37 | 38 | /** 会议室类型: 视频/其他. */ 39 | @Column(nullable = false) 40 | private String type; 41 | 42 | /** 会议室视频设备的厂商,可选:华为/思科. */ 43 | private String vnedor; 44 | 45 | /** 会议室视频设备的ip,可选. */ 46 | private String ip; 47 | 48 | /** 会议室视频设备终端 id,可选. */ 49 | private String terminalId; 50 | 51 | public Long getId() { 52 | return id; 53 | } 54 | 55 | public void setId(Long id) { 56 | this.id = id; 57 | } 58 | 59 | public String getRoomNo() { 60 | return roomNo; 61 | } 62 | 63 | public void setRoomNo(String roomNo) { 64 | this.roomNo = roomNo; 65 | } 66 | 67 | public String getSize() { 68 | return size; 69 | } 70 | 71 | public void setSize(String size) { 72 | this.size = size; 73 | } 74 | 75 | public String getType() { 76 | return type; 77 | } 78 | 79 | public void setType(String type) { 80 | this.type = type; 81 | } 82 | 83 | public String getVnedor() { 84 | return vnedor; 85 | } 86 | 87 | public void setVnedor(String vnedor) { 88 | this.vnedor = vnedor; 89 | } 90 | 91 | public String getIp() { 92 | return ip; 93 | } 94 | 95 | public void setIp(String ip) { 96 | this.ip = ip; 97 | } 98 | 99 | public String getTerminalId() { 100 | return terminalId; 101 | } 102 | 103 | public void setTerminalId(String terminalId) { 104 | this.terminalId = terminalId; 105 | } 106 | 107 | public Long getTeamId() { 108 | return teamId; 109 | } 110 | 111 | public void setTeamId(Long teamId) { 112 | this.teamId = teamId; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/MeetingRoomRepository.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | public interface MeetingRoomRepository extends JpaRepository { 8 | 9 | List findByTeamId(Long id); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/Participant.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.Id; 9 | import javax.persistence.JoinColumn; 10 | import javax.persistence.ManyToOne; 11 | 12 | import com.fasterxml.jackson.annotation.JsonFormat; 13 | import com.fasterxml.jackson.annotation.JsonIgnore; 14 | 15 | @Entity 16 | public class Participant implements Serializable { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | 24 | /** 排期 id. */ 25 | @JsonIgnore 26 | @ManyToOne 27 | @JoinColumn(name = "schedule_id") 28 | private Schedule schedule; 29 | 30 | /** 参会人微信 openId. */ 31 | private String openId; 32 | 33 | /** 参会人微信昵名. */ 34 | private String nickName; 35 | 36 | /** 参会人微信头像的链接. */ 37 | private String avatarUrl; 38 | 39 | /** 参会日期. */ 40 | private Date date; 41 | 42 | /** 参加会议表格提交的 formId 用于模板消息通知. */ 43 | private String formId; 44 | 45 | public Long getId() { 46 | return id; 47 | } 48 | 49 | public void setId(Long id) { 50 | this.id = id; 51 | } 52 | 53 | public Schedule getSchedule() { 54 | return schedule; 55 | } 56 | 57 | public void setSchedule(Schedule schedule) { 58 | this.schedule = schedule; 59 | } 60 | 61 | public String getNickName() { 62 | return nickName; 63 | } 64 | 65 | public void setNickName(String nickName) { 66 | this.nickName = nickName; 67 | } 68 | 69 | public String getAvatarUrl() { 70 | return avatarUrl; 71 | } 72 | 73 | public void setAvatarUrl(String avatarUrl) { 74 | this.avatarUrl = avatarUrl; 75 | } 76 | 77 | public String getOpenId() { 78 | return openId; 79 | } 80 | 81 | public void setOpenId(String openId) { 82 | this.openId = openId; 83 | } 84 | 85 | @JsonFormat(pattern = "yyyy-MM-dd") 86 | public Date getDate() { 87 | return date; 88 | } 89 | 90 | @JsonFormat(pattern = "yyyy-MM-dd") 91 | public void setDate(Date date) { 92 | this.date = date; 93 | } 94 | 95 | public String getFormId() { 96 | return formId; 97 | } 98 | 99 | public void setFormId(String formId) { 100 | this.formId = formId; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/ParticipantRepository.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import org.springframework.data.repository.PagingAndSortingRepository; 4 | 5 | public interface ParticipantRepository extends PagingAndSortingRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/Schedule.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.io.Serializable; 4 | import java.io.UnsupportedEncodingException; 5 | import java.net.URLDecoder; 6 | import java.net.URLEncoder; 7 | import java.util.ArrayList; 8 | import java.util.Date; 9 | import java.util.List; 10 | 11 | import javax.persistence.CascadeType; 12 | import javax.persistence.Column; 13 | import javax.persistence.Entity; 14 | import javax.persistence.FetchType; 15 | import javax.persistence.GeneratedValue; 16 | import javax.persistence.Id; 17 | import javax.persistence.ManyToOne; 18 | import javax.persistence.OneToMany; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import com.fasterxml.jackson.annotation.JsonFormat; 24 | 25 | /** 26 | * 会议室排期. 27 | * 28 | * @author devzzm 29 | */ 30 | @Entity 31 | public class Schedule implements Serializable, Comparable { 32 | 33 | private static final long serialVersionUID = 1L; 34 | 35 | private static final Logger logger = LoggerFactory.getLogger(Schedule.class); 36 | 37 | /** 排期的重复模式:不重复. */ 38 | public static final String REPEAT_MODE_NO = "N"; 39 | 40 | /** 排期的重复模式:按周. */ 41 | public static final String REPEAT_MODE_WEEKLY = "W"; 42 | 43 | @Id 44 | @GeneratedValue 45 | private Long id; 46 | 47 | /** 会议的标题. */ 48 | @Column(nullable = false, length = 1024) 49 | private String title; 50 | 51 | /** 会议室房间编号. */ 52 | @ManyToOne(fetch = FetchType.EAGER, optional = false) 53 | private MeetingRoom meetingRoom; 54 | 55 | /** 预订日期. */ 56 | @Column(nullable = false) 57 | private Date date; 58 | 59 | /** 开始时间,格式为 'HH:mm:ss'. */ 60 | @Column(nullable = false) 61 | private String startTime; 62 | 63 | /** 结束时间.格式为 'HH:mm:ss'. */ 64 | @Column(nullable = false) 65 | private String endTime; 66 | 67 | /** 创建者的微信 openId */ 68 | @Column(nullable = false) 69 | private String creatorOpenId; 70 | 71 | /** 创建者的微信昵称. */ 72 | @Column(nullable = false) 73 | private String creatorNickName; 74 | 75 | /** 创建者的微信头像的链接. */ 76 | @Column(nullable = false, length = 1024) 77 | private String creatorAvatarUrl; 78 | 79 | @Column(nullable = false) 80 | /** 会议重复的模式: N-不重复/W-每周. */ 81 | private String repeatMode; 82 | 83 | @OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "schedule") 84 | private List participants = new ArrayList<>(); 85 | 86 | public Long getId() { 87 | return id; 88 | } 89 | 90 | public void setId(Long id) { 91 | this.id = id; 92 | } 93 | 94 | public String getTitle() { 95 | return title; 96 | } 97 | 98 | public void setTitle(String title) { 99 | this.title = title; 100 | } 101 | 102 | public MeetingRoom getMeetingRoom() { 103 | return meetingRoom; 104 | } 105 | 106 | @JsonFormat(pattern = "yyyy-MM-dd") 107 | public Date getDate() { 108 | return date; 109 | } 110 | 111 | @JsonFormat(pattern = "yyyy-MM-dd") 112 | public void setDate(Date date) { 113 | this.date = date; 114 | } 115 | 116 | public String getStartTime() { 117 | return startTime; 118 | } 119 | 120 | public void setStartTime(String startTime) { 121 | this.startTime = startTime; 122 | } 123 | 124 | public String getEndTime() { 125 | return endTime; 126 | } 127 | 128 | public void setEndTime(String endTime) { 129 | this.endTime = endTime; 130 | } 131 | 132 | public String getCreatorNickName() { 133 | try { 134 | return null == creatorNickName ? null : URLDecoder.decode(this.creatorNickName, "utf-8"); 135 | } catch (UnsupportedEncodingException e) { 136 | logger.warn("decode creatorNickName fail:", e); 137 | return this.creatorNickName; 138 | } 139 | } 140 | 141 | public void setCreatorNickName(String creatorNickName) { 142 | try { 143 | this.creatorNickName = URLEncoder.encode(creatorNickName, "utf-8"); 144 | } catch (UnsupportedEncodingException e) { 145 | logger.warn("encode creatorNickName fail:", e); 146 | this.creatorNickName = creatorNickName; 147 | } 148 | } 149 | 150 | public String getCreatorAvatarUrl() { 151 | return creatorAvatarUrl; 152 | } 153 | 154 | public void setCreatorAvatarUrl(String creatorAvatarUrl) { 155 | this.creatorAvatarUrl = creatorAvatarUrl; 156 | } 157 | 158 | public String getRepeatMode() { 159 | return repeatMode; 160 | } 161 | 162 | public void setRepeatMode(String repeatMode) { 163 | this.repeatMode = repeatMode; 164 | } 165 | 166 | public List getParticipants() { 167 | return participants; 168 | } 169 | 170 | public void setParticipants(List participants) { 171 | this.participants = participants; 172 | } 173 | 174 | public void setMeetingRoom(MeetingRoom meetingRoom) { 175 | this.meetingRoom = meetingRoom; 176 | } 177 | 178 | public String getCreatorOpenId() { 179 | return creatorOpenId; 180 | } 181 | 182 | public void setCreatorOpenId(String creatorOpenId) { 183 | this.creatorOpenId = creatorOpenId; 184 | } 185 | 186 | public void addParticipant(Participant participant) { 187 | participant.setSchedule(this); 188 | this.getParticipants().add(participant); 189 | } 190 | 191 | /** 192 | * @return 当排期的重复模式为按周重复返回 true 193 | */ 194 | public boolean isRepeatModeWeekly() { 195 | return Schedule.REPEAT_MODE_WEEKLY.equals(getRepeatMode()); 196 | } 197 | 198 | /** 199 | * 判断当前排期和指定的排期是否有冲突. 200 | * 201 | * @param schedule 202 | * 指定用来判断是否冲突的排期 203 | * @return 有冲突返回 true 204 | */ 205 | public boolean isConflict(Schedule schedule) { 206 | 207 | // 同一个排期肯定不算冲突 208 | if (getId().equals(schedule.getId())) { 209 | return false; 210 | } 211 | 212 | if (date.compareTo(schedule.getDate()) != 0) { 213 | return false; 214 | } 215 | if (startTime.compareTo(schedule.getStartTime()) >= 0 && startTime.compareTo(schedule.getEndTime()) < 0) { 216 | return true; 217 | } 218 | if (endTime.compareTo(schedule.getStartTime()) > 0 && endTime.compareTo(schedule.getEndTime()) <= 0) { 219 | return true; 220 | } 221 | if (startTime.compareTo(schedule.getStartTime()) < 0 && endTime.compareTo(schedule.getEndTime()) > 0) { 222 | return true; 223 | } 224 | return false; 225 | } 226 | 227 | @Override 228 | public int compareTo(Schedule o) { 229 | if (this.date.compareTo(o.getDate()) == 0) { 230 | return this.startTime.compareTo(o.getStartTime()); 231 | } else { 232 | return this.date.compareTo(o.getDate()); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/ScheduleRepository.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | public interface ScheduleRepository extends JpaRepository, ScheduleRepositoryCustom { 9 | 10 | List findByMeetingRoomAndDate(MeetingRoom meetingRoom, Date date); 11 | 12 | List findByMeetingRoomAndRepeatMode(MeetingRoom meetingRoom, String repeatMode); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/ScheduleRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.catframework.agileworking.vo.ScheduleVO; 7 | 8 | /** 9 | * 自定义 {@link ScheduleRepository} 接口 10 | * 11 | * @author devzzm 12 | */ 13 | public interface ScheduleRepositoryCustom { 14 | 15 | /** 16 | * 查询指定日期指定 openId 的排期,使用了 native sql 来实现. 17 | * 18 | * @param openId 微信 openId 19 | * @param date 查询的日期 20 | * @return 符合条件的排期值对象 21 | */ 22 | List findByOpenIdAndDate(String openId, Date date); 23 | 24 | 25 | /** 26 | * 查询指定日期所有的排期 27 | * @param date 查询的日期 28 | * @return 符合条件的排期值对象 29 | */ 30 | List findByDate(Date date); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/ScheduleRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import javax.persistence.EntityManager; 8 | import javax.transaction.Transactional; 9 | 10 | import org.catframework.agileworking.vo.ScheduleVO; 11 | import org.hibernate.SQLQuery; 12 | import org.hibernate.Session; 13 | import org.hibernate.transform.Transformers; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | 16 | public class ScheduleRepositoryImpl implements ScheduleRepositoryCustom { 17 | 18 | @Autowired 19 | private EntityManager entityManager; 20 | 21 | /** 22 | * 自定义 native query 查询 {@link ScheduleVO} ,略繁琐但是好像没有更好的办法. 23 | */ 24 | @Transactional 25 | @Override 26 | public List findByOpenIdAndDate(String openId, Date date) { 27 | String sql = "select t.schedule_id as scheduleId ,t.date,t.meeting_room_id as meetingRoomId,t.title,t.open_id as openId,m.room_no as roomNo,t.start_time as startTime,t.end_time as endTime, t.repeat_mode as repeatMode from (select p.schedule_id,p.date,s.meeting_room_id,s.title,p.open_id,s.start_time,s.end_time,s.repeat_mode from participant p left join schedule s on p.schedule_id = s.id ) as t left join meeting_room m on t.meeting_room_id = m.id where (t.open_id=? and t.date=?) or (t.open_id=? and repeat_mode='W')"; 28 | Session session = entityManager.unwrap(org.hibernate.Session.class); 29 | SQLQuery query = session.createSQLQuery(sql); 30 | @SuppressWarnings("unchecked") 31 | List scheduleVOs = query.setResultTransformer(Transformers.aliasToBean(ScheduleVO.class)) 32 | .setParameter(0, openId).setParameter(1, date).setParameter(2, openId).list(); 33 | return scheduleVOs.stream().filter(s -> s.isNeedInclude(date)).map(s -> { 34 | s.setDate(date); 35 | return s; 36 | }).sorted().collect(Collectors.toList()); 37 | } 38 | 39 | 40 | @Transactional 41 | @Override 42 | public List findByDate(Date date) { 43 | String sql = "select t.schedule_id as scheduleId ,t.date,t.meeting_room_id as meetingRoomId,t.title,t.open_id as openId,m.room_no as roomNo,t.start_time as startTime,t.end_time as endTime, t.repeat_mode as repeatMode from (select p.schedule_id,p.date,s.meeting_room_id,s.title, s.creator_open_id open_id,s.start_time,s.end_time,s.repeat_mode from participant p left join schedule s on p.schedule_id = s.id ) as t left join meeting_room m on t.meeting_room_id = m.id where (t.date=?) or (repeat_mode='W')"; 44 | Session session = entityManager.unwrap(org.hibernate.Session.class); 45 | SQLQuery query = session.createSQLQuery(sql); 46 | @SuppressWarnings("unchecked") 47 | List scheduleVOs = query.setResultTransformer(Transformers.aliasToBean(ScheduleVO.class)) 48 | .setParameter(0, date).list(); 49 | return scheduleVOs.stream().filter(s -> s.isNeedInclude(date)).map(s -> { 50 | s.setDate(date); 51 | return s; 52 | }).sorted().collect(Collectors.toList()); 53 | 54 | } 55 | 56 | public void setEntityManager(EntityManager entityManager) { 57 | this.entityManager = entityManager; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/Team.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import javax.persistence.CascadeType; 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.FetchType; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.Id; 13 | import javax.persistence.JoinColumn; 14 | import javax.persistence.JoinTable; 15 | import javax.persistence.ManyToMany; 16 | 17 | import com.fasterxml.jackson.annotation.JsonIgnore; 18 | 19 | @Entity 20 | public class Team implements Serializable { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | @Id 25 | @GeneratedValue 26 | private Long id; 27 | 28 | @Column(nullable = false) 29 | private String name; 30 | 31 | // desc 和 mysql 中的关键字有冲突,所以使用了 'team_desc' 32 | @Column(name = "team_desc") 33 | private String desc; 34 | 35 | /** 用于注册校验的 token */ 36 | private String token; 37 | 38 | @ManyToMany(cascade = { CascadeType.PERSIST}, fetch = FetchType.EAGER) 39 | @JoinTable(name = "team_user_mapping", joinColumns = { 40 | @JoinColumn(name = "team_id", referencedColumnName = "id") }, inverseJoinColumns = { 41 | @JoinColumn(name = "user_id", referencedColumnName = "id") }) 42 | @JsonIgnore 43 | private List users = new ArrayList<>(); 44 | 45 | public Long getId() { 46 | return id; 47 | } 48 | 49 | public void setId(Long id) { 50 | this.id = id; 51 | } 52 | 53 | public String getName() { 54 | return name; 55 | } 56 | 57 | public void setName(String name) { 58 | this.name = name; 59 | } 60 | 61 | public String getDesc() { 62 | return desc; 63 | } 64 | 65 | public void setDesc(String desc) { 66 | this.desc = desc; 67 | } 68 | 69 | public List getUsers() { 70 | return users; 71 | } 72 | 73 | public void setUsers(List users) { 74 | this.users = users; 75 | } 76 | 77 | public void addUser(User user) { 78 | this.users.add(user); 79 | } 80 | 81 | @JsonIgnore 82 | public String getToken() { 83 | return token; 84 | } 85 | 86 | public void setToken(String token) { 87 | this.token = token; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/TeamRepository.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface TeamRepository extends JpaRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/User.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | import javax.persistence.ManyToMany; 11 | 12 | import com.fasterxml.jackson.annotation.JsonIgnore; 13 | 14 | @Entity 15 | public class User implements Serializable { 16 | 17 | private static final long serialVersionUID = -726326819568372268L; 18 | 19 | @Id 20 | @GeneratedValue 21 | private Long id; 22 | 23 | @Column(nullable = false, length = 200) 24 | private String name; 25 | 26 | @Column(nullable = false) 27 | private String mobileNo; 28 | 29 | @Column(nullable = false, unique = true) 30 | private String openId; 31 | 32 | @Column(nullable = false) 33 | private String nickName; 34 | 35 | @Column(nullable = false) 36 | private String avatarUrl; 37 | 38 | @ManyToMany(mappedBy="users") 39 | @JsonIgnore 40 | private List teams; 41 | 42 | public Long getId() { 43 | return id; 44 | } 45 | 46 | public void setId(Long id) { 47 | this.id = id; 48 | } 49 | 50 | public String getName() { 51 | return name; 52 | } 53 | 54 | public void setName(String name) { 55 | this.name = name; 56 | } 57 | 58 | public String getMobileNo() { 59 | return mobileNo; 60 | } 61 | 62 | public void setMobileNo(String mobileNo) { 63 | this.mobileNo = mobileNo; 64 | } 65 | 66 | public String getOpenId() { 67 | return openId; 68 | } 69 | 70 | public void setOpenId(String openId) { 71 | this.openId = openId; 72 | } 73 | 74 | public String getNickName() { 75 | return nickName; 76 | } 77 | 78 | public void setNickName(String nickName) { 79 | this.nickName = nickName; 80 | } 81 | 82 | public String getAvatarUrl() { 83 | return avatarUrl; 84 | } 85 | 86 | public void setAvatarUrl(String avatarUrl) { 87 | this.avatarUrl = avatarUrl; 88 | } 89 | 90 | public List getTeams() { 91 | return teams; 92 | } 93 | 94 | public void setTeams(List teams) { 95 | this.teams = teams; 96 | } 97 | 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface UserRepository extends JpaRepository { 6 | 7 | User findOneByOpenId(String openId); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/domain/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 实体及领域模型 3 | * @author devzzm 4 | */ 5 | package org.catframework.agileworking.domain; -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/scheduling/SendNotifyMessageJob.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.scheduling; 2 | 3 | import java.util.Date; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import org.apache.commons.logging.Log; 8 | import org.apache.commons.logging.LogFactory; 9 | import org.catframework.agileworking.domain.Schedule; 10 | import org.catframework.agileworking.domain.ScheduleRepository; 11 | import org.catframework.agileworking.domain.User; 12 | import org.catframework.agileworking.domain.UserRepository; 13 | import org.catframework.agileworking.utils.DateUtils; 14 | import org.catframework.agileworking.utils.JsonUtils; 15 | import org.catframework.agileworking.vo.NotifyMessageTemplate; 16 | import org.catframework.agileworking.vo.ScheduleVO; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.beans.factory.annotation.Value; 19 | import org.springframework.scheduling.annotation.Scheduled; 20 | import org.springframework.stereotype.Component; 21 | import org.springframework.web.client.RestTemplate; 22 | 23 | /** 24 | * 发送会议通知,每60秒轮训一次,在会前 15 分钟发送提醒,目前的实现比较简单仅在内存中增加了缓存来存放已发送的排期. 25 | * 26 | * 提示:目前版本只考虑了单 JVM 环境运行,作业调度未考虑并发的问题,如果运行在集群环境下,需要修改此类. 27 | * 28 | * @author devzzm 29 | */ 30 | @Component 31 | public class SendNotifyMessageJob { 32 | 33 | private static final Log logger = LogFactory.getLog(SendNotifyMessageJob.class); 34 | 35 | @Value("${wechat.app-id}") 36 | private String appId; 37 | 38 | @Value("${wechat.app-secret}") 39 | private String appSecret; 40 | 41 | @Value("${wechat.notify.template-id}") 42 | private String templateId; 43 | 44 | @Value("${wecaht.acquire-access-token-url}") 45 | private String acquireAccessTokenUrl; 46 | 47 | @Value("${wecaht.send-messag-url}") 48 | private String sendMessageUrl; 49 | 50 | @Autowired 51 | private ScheduleRepository scheduleRepository; 52 | 53 | @Autowired 54 | private UserRepository userRepository; 55 | 56 | private RestTemplate restTemplate = new RestTemplate(); 57 | 58 | private Set cache = new HashSet<>(); 59 | 60 | private Date date = DateUtils.parse(DateUtils.format(new Date(), DateUtils.PATTERN_SIMPLE_DATE), 61 | DateUtils.PATTERN_SIMPLE_DATE); 62 | 63 | @Scheduled(fixedDelay = 60 * 1000) 64 | public void execute() { 65 | Date date = DateUtils.parse(DateUtils.format(new Date(), DateUtils.PATTERN_SIMPLE_DATE), 66 | DateUtils.PATTERN_SIMPLE_DATE); 67 | if (date.compareTo(this.date) > 0) { 68 | this.date = date; 69 | cache.clear(); 70 | } 71 | 72 | String accessToken = getAccessToken(); 73 | scheduleRepository.findByDate(date).forEach(s -> { 74 | if (cache.contains(new Long(s.getScheduleId().intValue()))) { 75 | return; 76 | } 77 | if (isNeedSendMessageNow(s)) { 78 | doSend(accessToken, s); 79 | cache.add(new Long(s.getScheduleId().intValue())); 80 | } 81 | }); 82 | } 83 | 84 | private void doSend(String accessToken, ScheduleVO s) { 85 | Schedule schedule = scheduleRepository.findOne(new Long(s.getScheduleId().intValue())); 86 | User creator = userRepository.findOneByOpenId(s.getOpenId()); 87 | schedule.getParticipants().stream().forEach(p -> { 88 | NotifyMessageTemplate template = new NotifyMessageTemplate(creator, schedule, p, templateId); 89 | String result = restTemplate.postForObject(sendMessageUrl, template.toTemplateMessage(), String.class, 90 | accessToken); 91 | logger.info("send notify message result is :" + result); 92 | }); 93 | } 94 | 95 | /* 会议开始前15分钟开始发送通知. */ 96 | private boolean isNeedSendMessageNow(ScheduleVO scheduleVO) { 97 | long now = System.currentTimeMillis(); 98 | return (now + 15 * 60 * 1000) > DateUtils.parse( 99 | DateUtils.format(scheduleVO.getDate(), DateUtils.PATTERN_SIMPLE_DATE) + " " + scheduleVO.getStartTime(), 100 | DateUtils.PATTERN_SIMPLE_DATE + " HH:mm").getTime(); 101 | } 102 | 103 | private String getAccessToken() { 104 | String result = restTemplate.getForObject(acquireAccessTokenUrl, String.class, appId, appSecret); 105 | return (String) JsonUtils.decode(result).get("access_token"); 106 | 107 | } 108 | 109 | public void setAppId(String appId) { 110 | this.appId = appId; 111 | } 112 | 113 | public void setAppSecret(String appSecret) { 114 | this.appSecret = appSecret; 115 | } 116 | 117 | public void setTemplateId(String templateId) { 118 | this.templateId = templateId; 119 | } 120 | 121 | public void setScheduleRepository(ScheduleRepository scheduleRepository) { 122 | this.scheduleRepository = scheduleRepository; 123 | } 124 | 125 | public void setUserRepository(UserRepository userRepository) { 126 | this.userRepository = userRepository; 127 | } 128 | 129 | public void setRestTemplate(RestTemplate restTemplate) { 130 | this.restTemplate = restTemplate; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/service/ScheduleService.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.service; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.catframework.agileworking.domain.Schedule; 7 | 8 | public interface ScheduleService { 9 | 10 | /** 11 | * 查找指定会议室指定日期区间的排期,会自动对按周重复的排期进行计算. 12 | * 13 | * @param meetingRoomId 会议室 id 14 | * @param date 指定日期 15 | * @return 符合条件的排期列表 16 | */ 17 | List find(Long meetingRoomId, Date date); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/service/WebTokenService.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.service; 2 | 3 | /** 4 | * 小程序的会话使用 WebToken 的方式跟踪,非使用传统的 session ,此服务提供 WebToken 相关的功能. 5 | * 6 | * @author devzzm 7 | */ 8 | public interface WebTokenService { 9 | 10 | /** 11 | * 使用指定的主题生成 token 12 | * 13 | * @param subject 指定的信息 14 | * @return 生成的 token 15 | */ 16 | String generate(String subject); 17 | 18 | /** 19 | * 校验指定的主题的 token 是否福匹配 20 | * 21 | * @param token 被校验的 token 22 | * @return 当校验成功时返回 true 23 | */ 24 | boolean verify(String subject, String token); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/service/impl/ScheduleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.service.impl; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import org.catframework.agileworking.domain.MeetingRoom; 8 | import org.catframework.agileworking.domain.MeetingRoomRepository; 9 | import org.catframework.agileworking.domain.Schedule; 10 | import org.catframework.agileworking.domain.ScheduleRepository; 11 | import org.catframework.agileworking.service.ScheduleService; 12 | import org.catframework.agileworking.utils.DateUtils; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Component; 15 | 16 | @Component 17 | public class ScheduleServiceImpl implements ScheduleService { 18 | 19 | @Autowired 20 | private ScheduleRepository scheduleRepository; 21 | 22 | @Autowired 23 | private MeetingRoomRepository meetingRoomRepository; 24 | 25 | @Override 26 | public List find(Long meetingRoomId, Date date) { 27 | MeetingRoom meetingRoom = meetingRoomRepository.findOne(meetingRoomId); 28 | List schedules = scheduleRepository.findByMeetingRoomAndDate(meetingRoom, date); 29 | List weeklySchedules = scheduleRepository.findByMeetingRoomAndRepeatMode(meetingRoom, 30 | Schedule.REPEAT_MODE_WEEKLY); 31 | weeklySchedules.stream().forEach((s1) -> { 32 | if (schedules.stream().noneMatch((s2) -> s1.getId().equals(s2.getId()))) { 33 | if (s1.getDate().compareTo(date) < 0) { 34 | if (DateUtils.isSameWeekOfday(s1.getDate(), date)) { 35 | s1.setDate(date); 36 | schedules.add(s1); 37 | } 38 | } 39 | } 40 | }); 41 | // 按照开始时间进行排序 42 | return schedules.stream().sorted((s1, s2) -> s1.getStartTime().compareTo(s2.getStartTime())) 43 | .collect(Collectors.toList()); 44 | } 45 | 46 | public void setScheduleRepository(ScheduleRepository scheduleRepository) { 47 | this.scheduleRepository = scheduleRepository; 48 | } 49 | 50 | public void setMeetingRoomRepository(MeetingRoomRepository meetingRoomRepository) { 51 | this.meetingRoomRepository = meetingRoomRepository; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/service/impl/SimpleJJWTWebTokenServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.service.impl; 2 | 3 | import java.security.Key; 4 | 5 | import org.apache.commons.logging.Log; 6 | import org.apache.commons.logging.LogFactory; 7 | import org.catframework.agileworking.service.WebTokenService; 8 | import org.springframework.stereotype.Component; 9 | 10 | import io.jsonwebtoken.Jwts; 11 | import io.jsonwebtoken.SignatureAlgorithm; 12 | import io.jsonwebtoken.impl.crypto.MacProvider; 13 | 14 | /** 15 | * 使用 JJWT 做为 WebToken 的实现,当前的实现每次服务启动都生成一个新的 key,且不支持集群环境下使用,后续考虑配置文件或者数据库的方式. 16 | * 17 | * @author devzzm 18 | */ 19 | @Component 20 | public class SimpleJJWTWebTokenServiceImpl implements WebTokenService { 21 | 22 | private static final Log logger = LogFactory.getLog(SimpleJJWTWebTokenServiceImpl.class); 23 | 24 | private Key key = MacProvider.generateKey(); 25 | 26 | @Override 27 | public String generate(String subject) { 28 | return Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS512, key).compact(); 29 | } 30 | 31 | @Override 32 | public boolean verify(String subject, String token) { 33 | try { 34 | return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getSubject().equals(subject); 35 | } catch (Exception e) { 36 | logger.error("Verify fail:", e); 37 | return false; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/service/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 服务层 3 | * @author devzzm 4 | * 5 | */ 6 | package org.catframework.agileworking.service; -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.utils; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | 8 | /** 9 | * {@link Date} 解析、格式化的脚手架构工具类. 10 | * 11 | * @author devzzm 12 | * 13 | */ 14 | public final class DateUtils { 15 | 16 | /** 简单的日期格式: yyyy-MM-dd */ 17 | public static final String PATTERN_SIMPLE_DATE = "yyyy-MM-dd"; 18 | 19 | public static final Date parse(String source, String pattern) { 20 | try { 21 | return new SimpleDateFormat(pattern).parse(source); 22 | } catch (ParseException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | 27 | public static final String format(Date date, String pattern) { 28 | return new SimpleDateFormat(pattern).format(date); 29 | } 30 | 31 | /** 32 | * 判断指定的两个日期的所属的星期是否相同. 33 | * 34 | * @return 当两个日期相同时返回 true 35 | */ 36 | public static boolean isSameWeekOfday(Date d1, Date d2) { 37 | Calendar d1Cal = Calendar.getInstance(); 38 | d1Cal.setTime(d1); 39 | Calendar d2Cal = Calendar.getInstance(); 40 | d2Cal.setTime(d2); 41 | return d1Cal.get(Calendar.DAY_OF_WEEK) == d2Cal.get(Calendar.DAY_OF_WEEK); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/utils/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.utils; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | 8 | public final class JsonUtils { 9 | 10 | @SuppressWarnings("unchecked") 11 | public static final Map decode(String jsonString) { 12 | ObjectMapper mapper = new ObjectMapper(); 13 | try { 14 | return mapper.readValue(jsonString, Map.class); 15 | } catch (IOException e) { 16 | throw new RuntimeException("JSON解析失败", e); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/vo/NotifyMessageTemplate.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.vo; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.catframework.agileworking.domain.Participant; 7 | import org.catframework.agileworking.domain.Schedule; 8 | import org.catframework.agileworking.domain.User; 9 | import org.catframework.agileworking.utils.DateUtils; 10 | 11 | public class NotifyMessageTemplate { 12 | 13 | private String openId; 14 | 15 | private String templateId; 16 | 17 | private String formId; 18 | 19 | private String title; 20 | 21 | private String roomNo; 22 | 23 | private String time; 24 | 25 | private String participantInfo; 26 | 27 | private String creator; 28 | 29 | public NotifyMessageTemplate(User creator, Schedule schedule, Participant participant, String templateId) { 30 | this.openId = participant.getOpenId(); 31 | this.templateId = templateId; 32 | this.formId = participant.getFormId(); 33 | this.title = schedule.getTitle(); 34 | this.roomNo = schedule.getMeetingRoom().getRoomNo(); 35 | this.time = (DateUtils.format(schedule.getDate(), "yyyy年MM月dd日") + " " + schedule.getStartTime() + "-" 36 | + schedule.getEndTime()); 37 | this.participantInfo = schedule.getParticipants().size() + "人参加"; 38 | this.creator = creator.getNickName() + "/" + creator.getName()+"("+creator.getMobileNo()+")"; 39 | } 40 | 41 | public String getOpenId() { 42 | return openId; 43 | } 44 | 45 | public String getTemplateId() { 46 | return templateId; 47 | } 48 | 49 | public String getFormId() { 50 | return formId; 51 | } 52 | 53 | public String getTitle() { 54 | return title; 55 | } 56 | 57 | public String getRoomNo() { 58 | return roomNo; 59 | } 60 | 61 | public String getTime() { 62 | return time; 63 | } 64 | 65 | public String getParticipantInfo() { 66 | return participantInfo; 67 | } 68 | 69 | public String getCreator() { 70 | return creator; 71 | } 72 | 73 | /** 74 | * 按照微信小程序模板消息的数据标准进行数据转换. 75 | * 76 | * @return 微信小程序格式的模板消息 77 | */ 78 | public Map toTemplateMessage() { 79 | Map message = new HashMap<>(); 80 | message.put("touser", this.openId); 81 | message.put("template_id", this.templateId); 82 | message.put("form_id", this.formId); 83 | Map data = new HashMap<>(); 84 | Map keyword1 = new HashMap<>(); 85 | keyword1.put("value", this.title); 86 | data.put("keyword1", keyword1); 87 | Map keyword2 = new HashMap<>(); 88 | keyword2.put("value", this.time); 89 | data.put("keyword2", keyword2); 90 | 91 | Map keyword3 = new HashMap<>(); 92 | keyword3.put("value", this.roomNo); 93 | data.put("keyword3", keyword3); 94 | 95 | Map keyword4 = new HashMap<>(); 96 | keyword4.put("value", this.participantInfo); 97 | data.put("keyword4", keyword4); 98 | 99 | Map keyword5 = new HashMap<>(); 100 | keyword5.put("value", this.creator); 101 | data.put("keyword5", keyword5); 102 | message.put("data", data); 103 | return message; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/vo/ScheduleVO.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.vo; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Date; 5 | 6 | import org.catframework.agileworking.domain.Schedule; 7 | import org.catframework.agileworking.domain.ScheduleRepository; 8 | import org.catframework.agileworking.utils.DateUtils; 9 | 10 | import com.fasterxml.jackson.annotation.JsonFormat; 11 | 12 | /** 13 | * 排期 {@link Schedule} 的值对象聚合了排期及会议室相关的信息. 14 | * 15 | * @author devzzm 16 | * @see ScheduleRepository#findByOpenIdAndDate(String, Date) 17 | */ 18 | public class ScheduleVO implements Comparable { 19 | 20 | private BigInteger scheduleId; 21 | 22 | private Date date; 23 | 24 | private BigInteger meetingRoomId; 25 | 26 | private String title; 27 | 28 | private String openId; 29 | 30 | private String roomNo; 31 | 32 | private String startTime; 33 | 34 | private String endTime; 35 | 36 | private String repeatMode; 37 | 38 | public BigInteger getScheduleId() { 39 | return scheduleId; 40 | } 41 | 42 | public void setScheduleId(BigInteger scheduleId) { 43 | this.scheduleId = scheduleId; 44 | } 45 | 46 | @JsonFormat(pattern = "yyyy-MM-dd") 47 | public Date getDate() { 48 | return date; 49 | } 50 | 51 | public void setDate(Date date) { 52 | this.date = date; 53 | } 54 | 55 | public BigInteger getMeetingRoomId() { 56 | return meetingRoomId; 57 | } 58 | 59 | public void setMeetingRoomId(BigInteger meetingRoomId) { 60 | this.meetingRoomId = meetingRoomId; 61 | } 62 | 63 | public String getTitle() { 64 | return title; 65 | } 66 | 67 | public void setTitle(String title) { 68 | this.title = title; 69 | } 70 | 71 | public String getOpenId() { 72 | return openId; 73 | } 74 | 75 | public void setOpenId(String openId) { 76 | this.openId = openId; 77 | } 78 | 79 | public String getRoomNo() { 80 | return roomNo; 81 | } 82 | 83 | public void setRoomNo(String roomNo) { 84 | this.roomNo = roomNo; 85 | } 86 | 87 | public String getStartTime() { 88 | return startTime; 89 | } 90 | 91 | public void setStartTime(String startTime) { 92 | this.startTime = startTime; 93 | } 94 | 95 | public String getEndTime() { 96 | return endTime; 97 | } 98 | 99 | public void setEndTime(String endTime) { 100 | this.endTime = endTime; 101 | } 102 | 103 | public String getRepeatMode() { 104 | return repeatMode; 105 | } 106 | 107 | public void setRepeatMode(String repeatMode) { 108 | this.repeatMode = repeatMode; 109 | } 110 | 111 | /** 112 | * 判断当前的排期值对象是否同指定的日期具有相同的星期属性且排期日期小于指定的日期.
113 | * 此方法用于查询本人某一日的排期清单使用. 114 | * 115 | * @param date 指定日期 116 | * @return 当排期的日期同指定的日期的星期属性相同且日期小于指定的日期时返回 true 117 | */ 118 | public boolean isNeedInclude(Date date) { 119 | if (getDate().equals(date)) { 120 | return true; 121 | } 122 | // 排除未来的排期 123 | if (getDate().compareTo(date) > 0) { 124 | return false; 125 | } 126 | return DateUtils.isSameWeekOfday(date, getDate()); 127 | } 128 | 129 | @Override 130 | public int compareTo(ScheduleVO o) { 131 | int result = getStartTime().compareTo(o.getStartTime()); 132 | if (result == 0) { 133 | return getRoomNo().compareTo(o.getRoomNo()); 134 | } 135 | return result; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/MeetingRoomController.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import org.catframework.agileworking.domain.MeetingRoom; 8 | import org.catframework.agileworking.domain.MeetingRoomRepository; 9 | import org.catframework.agileworking.domain.Participant; 10 | import org.catframework.agileworking.domain.Schedule; 11 | import org.catframework.agileworking.domain.ScheduleRepository; 12 | import org.catframework.agileworking.domain.User; 13 | import org.catframework.agileworking.domain.UserRepository; 14 | import org.catframework.agileworking.service.ScheduleService; 15 | import org.catframework.agileworking.utils.DateUtils; 16 | import org.catframework.agileworking.web.support.DefaultResult; 17 | import org.catframework.agileworking.web.support.Result; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.format.annotation.DateTimeFormat; 20 | import org.springframework.util.Assert; 21 | import org.springframework.web.bind.annotation.PathVariable; 22 | import org.springframework.web.bind.annotation.RequestBody; 23 | import org.springframework.web.bind.annotation.RequestMapping; 24 | import org.springframework.web.bind.annotation.RequestMethod; 25 | import org.springframework.web.bind.annotation.RequestParam; 26 | import org.springframework.web.bind.annotation.RestController; 27 | 28 | @RestController 29 | public class MeetingRoomController { 30 | 31 | @Autowired 32 | private MeetingRoomRepository meetingRoomRepository; 33 | 34 | @Autowired 35 | private ScheduleRepository scheduleRepository; 36 | 37 | @Autowired 38 | private ScheduleService scheduleService; 39 | 40 | @Autowired 41 | private UserRepository userRepository; 42 | 43 | @RequestMapping(path = "/meetingRooms/{id}", method = RequestMethod.GET) 44 | public Result> list(@PathVariable Long id) { 45 | return DefaultResult.newResult(meetingRoomRepository.findByTeamId(id)); 46 | } 47 | 48 | /** 49 | * 创新排期,单 JVM 并发量可期的情况下,简单粗暴的使用 synchronized 来解决并发创建、更新排期的问题. 50 | * 51 | * @param id 会议室 id 52 | * @param schedule 新建的排期 53 | */ 54 | @RequestMapping(path = "/meetingRooms/{id}/schedule", method = RequestMethod.POST) 55 | public synchronized Result createOrUpdateSchedule(@PathVariable(name = "id") Long id, 56 | @RequestParam(name = "formId", required = false) String formId, @RequestBody Schedule schedule) { 57 | MeetingRoom meetingRoom = meetingRoomRepository.findOne(id); 58 | validate(id, schedule); 59 | if (null == schedule.getId()) { 60 | schedule.setMeetingRoom(meetingRoom); 61 | schedule.addParticipant(creatorAsParticipant(schedule, formId)); 62 | scheduleRepository.save(schedule); 63 | } else { 64 | Schedule s = scheduleRepository.findOne(schedule.getId()); 65 | Assert.notNull(s, "修改的排期不存在."); 66 | s.setTitle(schedule.getTitle()); 67 | s.setStartTime(schedule.getStartTime()); 68 | s.setEndTime(schedule.getEndTime()); 69 | s.setDate(schedule.getDate()); 70 | s.setRepeatMode(schedule.getRepeatMode()); 71 | scheduleRepository.save(s); 72 | } 73 | return DefaultResult.newResult(schedule); 74 | } 75 | 76 | private Participant creatorAsParticipant(Schedule schedule, String formId) { 77 | Participant p = new Participant(); 78 | p.setAvatarUrl(schedule.getCreatorAvatarUrl()); 79 | p.setDate(schedule.getDate()); 80 | p.setNickName(schedule.getCreatorNickName()); 81 | p.setSchedule(schedule); 82 | p.setOpenId(schedule.getCreatorOpenId()); 83 | p.setFormId(formId); 84 | return p; 85 | } 86 | 87 | /** 88 | * 取消已设置的排期. 89 | * 90 | * @param id 排期 id 91 | */ 92 | @RequestMapping(path = "/meetingRooms/schedule/{id}", method = RequestMethod.DELETE) 93 | public Result cancelSchedule(@PathVariable Long id) { 94 | Schedule schedule = scheduleRepository.findOne(id); 95 | scheduleRepository.delete(schedule); 96 | return DefaultResult.newResult(); 97 | } 98 | 99 | /** 100 | * 查询指定会议室下指定日期区间的排期. 101 | * 102 | * @param id 会议室 id 103 | * @param date 指定日期 104 | * @return 指定的会议室指定日期的排期列表 105 | */ 106 | @RequestMapping(path = "/meetingRooms/{id}/schedule", method = RequestMethod.GET) 107 | public Result> schedules(@PathVariable Long id, 108 | @RequestParam(name = "date") @DateTimeFormat(pattern = DateUtils.PATTERN_SIMPLE_DATE) Date date) { 109 | return DefaultResult.newResult(scheduleService.find(id, date).stream().map(s -> { 110 | User user = userRepository.findOneByOpenId(s.getCreatorOpenId()); 111 | s.setCreatorNickName(s.getCreatorNickName() + "/" + user.getName()); 112 | return s; 113 | }).collect(Collectors.toList())); 114 | } 115 | 116 | private void validate(Long id, Schedule schedule) { 117 | Assert.isTrue(schedule.getStartTime().compareTo(schedule.getEndTime()) < 0, "会议开始时间需小于结束时间."); 118 | Assert.isTrue(!scheduleService.find(id, schedule.getDate()).stream().anyMatch(s -> s.isConflict(schedule)), 119 | "同已有排期冲突."); 120 | } 121 | 122 | public void setMeetingRoomRepository(MeetingRoomRepository meetingRoomRepository) { 123 | this.meetingRoomRepository = meetingRoomRepository; 124 | } 125 | 126 | public void setScheduleRepository(ScheduleRepository scheduleRepository) { 127 | this.scheduleRepository = scheduleRepository; 128 | } 129 | 130 | public void setScheduleService(ScheduleService scheduleService) { 131 | this.scheduleService = scheduleService; 132 | } 133 | 134 | public void setUserRepository(UserRepository userRepository) { 135 | this.userRepository = userRepository; 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/ParticipantController.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.catframework.agileworking.domain.ScheduleRepository; 7 | import org.catframework.agileworking.utils.DateUtils; 8 | import org.catframework.agileworking.vo.ScheduleVO; 9 | import org.catframework.agileworking.web.support.DefaultResult; 10 | import org.catframework.agileworking.web.support.Result; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.format.annotation.DateTimeFormat; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.bind.annotation.RequestParam; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | @RestController 20 | public class ParticipantController { 21 | 22 | @Autowired 23 | private ScheduleRepository scheduleRepository; 24 | 25 | @RequestMapping(path = "/participant/{openId}", method = RequestMethod.GET) 26 | public Result> participants(@PathVariable String openId, 27 | @RequestParam(name = "date") @DateTimeFormat(pattern = DateUtils.PATTERN_SIMPLE_DATE) Date date) { 28 | return DefaultResult.newResult(scheduleRepository.findByOpenIdAndDate(openId, date)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/ScheduleController.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import java.util.Optional; 4 | 5 | import org.catframework.agileworking.domain.MeetingRoomRepository; 6 | import org.catframework.agileworking.domain.Participant; 7 | import org.catframework.agileworking.domain.Schedule; 8 | import org.catframework.agileworking.domain.ScheduleRepository; 9 | import org.catframework.agileworking.domain.TeamRepository; 10 | import org.catframework.agileworking.domain.User; 11 | import org.catframework.agileworking.domain.UserRepository; 12 | import org.catframework.agileworking.web.support.DefaultResult; 13 | import org.catframework.agileworking.web.support.Result; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.util.Assert; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.RequestBody; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RequestMethod; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | @RestController 23 | public class ScheduleController { 24 | 25 | @Autowired 26 | private ScheduleRepository scheduleRepository; 27 | 28 | @Autowired 29 | private MeetingRoomRepository meetingRoomRepository; 30 | 31 | @Autowired 32 | private TeamRepository teamRepository; 33 | 34 | @Autowired 35 | private UserRepository userRepository; 36 | 37 | @RequestMapping(path = "/schedules/{id}/join", method = RequestMethod.POST) 38 | public Result join(@PathVariable Long id, @RequestBody Participant participant) { 39 | Schedule schedule = scheduleRepository.findOne(id); 40 | // 根据 MeetingRoom 找到 Team 找到其下的 Users 判断是否当前用户已加入其中 41 | Optional user = teamRepository 42 | .findOne(meetingRoomRepository.findOne(schedule.getMeetingRoom().getId()).getTeamId()).getUsers() 43 | .stream().filter(s -> s.getOpenId().equals(participant.getOpenId())).findAny(); 44 | Assert.isTrue(user.isPresent(), "用户未绑定."); 45 | if (!schedule.getParticipants().stream().anyMatch((p) -> { 46 | return p.getOpenId().equals(participant.getOpenId()); 47 | })) { 48 | schedule.addParticipant(participant); 49 | scheduleRepository.save(schedule); 50 | return DefaultResult.newResult(schedule); 51 | } else { 52 | throw new RuntimeException("您已加入过此会议啦."); 53 | } 54 | } 55 | 56 | @RequestMapping(path = "/schedules/{id}", method = RequestMethod.GET) 57 | public Result get(@PathVariable Long id) { 58 | Schedule schedule = scheduleRepository.findOne(id); 59 | User user = userRepository.findOneByOpenId(schedule.getCreatorOpenId()); 60 | schedule.setCreatorNickName(schedule.getCreatorNickName()+"/"+user.getName()); 61 | return DefaultResult.newResult(schedule); 62 | } 63 | 64 | public void setScheduleRepository(ScheduleRepository scheduleRepository) { 65 | this.scheduleRepository = scheduleRepository; 66 | } 67 | 68 | public void setMeetingRoomRepository(MeetingRoomRepository meetingRoomRepository) { 69 | this.meetingRoomRepository = meetingRoomRepository; 70 | } 71 | 72 | public void setTeamRepository(TeamRepository teamRepository) { 73 | this.teamRepository = teamRepository; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/TeamController.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.catframework.agileworking.domain.Team; 7 | import org.catframework.agileworking.domain.TeamRepository; 8 | import org.catframework.agileworking.domain.User; 9 | import org.catframework.agileworking.domain.UserRepository; 10 | import org.catframework.agileworking.service.WebTokenService; 11 | import org.catframework.agileworking.web.support.DefaultResult; 12 | import org.catframework.agileworking.web.support.Result; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.util.Assert; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestMethod; 19 | import org.springframework.web.bind.annotation.RequestParam; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | @RestController 23 | public class TeamController { 24 | 25 | @Autowired 26 | private TeamRepository teamRepository; 27 | 28 | @Autowired 29 | private UserRepository userRepository; 30 | 31 | @Autowired 32 | private WebTokenService webTokenService; 33 | 34 | // 加入团队 35 | @RequestMapping(path = "/team/{id}/join", method = RequestMethod.POST) 36 | public Result join(@PathVariable Long id, @RequestBody User user, 37 | @RequestParam(name = "token") String token) { 38 | Team team = teamRepository.findOne(id); 39 | Assert.notNull(team, "团队不存在."); 40 | Assert.isTrue(team.getToken().equals(token), "口令校验失败."); 41 | if (user.getId() == null) { 42 | userRepository.save(user); 43 | } 44 | if (team.getUsers().stream().noneMatch(u -> user.getId().equals(u.getId()))) { 45 | team.addUser(user); 46 | teamRepository.save(team); 47 | return DefaultResult.newResult(user).setHeader("token", webTokenService.generate(user.getOpenId())); 48 | } else { 49 | throw new RuntimeException("不可重复加入团队."); 50 | } 51 | 52 | } 53 | 54 | // 查询所有的团队 55 | @RequestMapping(path = "/teams", method = RequestMethod.GET) 56 | public Result> findAll() { 57 | return DefaultResult.newResult(teamRepository.findAll()); 58 | } 59 | 60 | // 根据团队 id 及 微信 openId 查询绑定的用户 61 | @RequestMapping(path = "/team/{teamId}/user/{openId}", method = RequestMethod.GET) 62 | public Result getUser(@PathVariable(name = "teamId") Long teamId, 63 | @PathVariable(name = "openId") String openId) { 64 | Team team = teamRepository.findOne(teamId); 65 | Optional optional = team.getUsers().stream().filter(s -> s.getOpenId().equals(openId)).findAny(); 66 | if (optional.isPresent()) { 67 | String token = webTokenService.generate(optional.get().getOpenId()); 68 | return DefaultResult.newResult(optional.get()).setHeader("token", token); 69 | } else { 70 | return DefaultResult.newFailResult("用户未绑定."); 71 | } 72 | } 73 | 74 | public void setTeamRepository(TeamRepository teamRepository) { 75 | this.teamRepository = teamRepository; 76 | } 77 | 78 | public void setUserRepository(UserRepository userRepository) { 79 | this.userRepository = userRepository; 80 | } 81 | 82 | public void setWebTokenService(WebTokenService webTokenService) { 83 | this.webTokenService = webTokenService; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/WechatController.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import java.util.Map; 4 | 5 | import org.catframework.agileworking.utils.JsonUtils; 6 | import org.catframework.agileworking.web.support.DefaultResult; 7 | import org.catframework.agileworking.web.support.Result; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.util.Assert; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | import org.springframework.web.bind.annotation.RestController; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | @RestController 17 | public class WechatController { 18 | 19 | @Value("${wechat.app-id}") 20 | private String appId; 21 | 22 | @Value("${wechat.app-secret}") 23 | private String appSecret; 24 | 25 | @Value("${wecaht.jscode2session-url}") 26 | private String jscode2sessionUrl; 27 | 28 | private RestTemplate restTemplate = new RestTemplate(); 29 | 30 | @RequestMapping(path = "/wechat/openid/{jsCode}", method = RequestMethod.GET) 31 | public Result getOpenId(@PathVariable String jsCode) { 32 | Map result = JsonUtils 33 | .decode(restTemplate.getForObject(jscode2sessionUrl, String.class, appId, appSecret, jsCode)); 34 | String openid = (String) result.get("openid"); 35 | Assert.hasLength(openid, "获取openid失败:" + result); 36 | return DefaultResult.newResult(openid); 37 | } 38 | 39 | public void setAppId(String appId) { 40 | this.appId = appId; 41 | } 42 | 43 | public void setAppSecret(String appSecret) { 44 | this.appSecret = appSecret; 45 | } 46 | 47 | public void setJscode2sessionUrl(String jscode2sessionUrl) { 48 | this.jscode2sessionUrl = jscode2sessionUrl; 49 | } 50 | 51 | public void setRestTemplate(RestTemplate restTemplate) { 52 | this.restTemplate = restTemplate; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Web 层 3 | * @author devzzm 4 | * 5 | */ 6 | package org.catframework.agileworking.web; -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/support/DefaultResult.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web.support; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.catframework.agileworking.BusinessException; 7 | import org.catframework.agileworking.common.ResponseCodes; 8 | 9 | public class DefaultResult implements Result { 10 | 11 | /** 12 | * 创建一个成功的结果,不含 payload 13 | */ 14 | public static Result newResult() { 15 | DefaultResult result = new DefaultResult<>(); 16 | result.status = Result.STATUS_SUCCESS; 17 | result.responseCode = ResponseCodes.RESPONSE_CODE_SUCCESS; 18 | return result; 19 | } 20 | 21 | /** 22 | * 创建一个成功的结果 23 | * 24 | * @param payload 结果中的数据 25 | * @return 新创建的交易结果 26 | */ 27 | public static Result newResult(T payload) { 28 | DefaultResult result = new DefaultResult<>(); 29 | result.status = Result.STATUS_SUCCESS; 30 | result.responseCode = ResponseCodes.RESPONSE_CODE_SUCCESS; 31 | result.payload = payload; 32 | return result; 33 | } 34 | 35 | /** 36 | * 创建一个失败的结果 37 | * 38 | * @param ex 导致交易失败的具体异常 39 | * @return 新创建的交易结果 40 | */ 41 | public static Result newFailResult(Throwable ex) { 42 | DefaultResult result = new DefaultResult<>(); 43 | result.status = Result.STATUS_FAIL; 44 | result.responseMessage = ex.getMessage(); 45 | result.responseCode = (ex instanceof BusinessException) ? ((BusinessException) ex).getCode() 46 | : ResponseCodes.RESPONSE_CODE_SYSTEM_ERROR; 47 | return result; 48 | } 49 | 50 | /** 51 | * 创建一个具有指定错误消息的失败结果 52 | * 53 | * @param message 错误消息 54 | * @return 新创建的交易结果 55 | */ 56 | public static Result newFailResult(String message) { 57 | DefaultResult result = new DefaultResult<>(); 58 | result.status = Result.STATUS_FAIL; 59 | result.responseMessage = message; 60 | result.responseCode = ResponseCodes.RESPONSE_CODE_SYSTEM_ERROR; 61 | return result; 62 | } 63 | 64 | private DefaultResult() { 65 | } 66 | 67 | private Map headers = new HashMap<>(); 68 | 69 | private int status; 70 | 71 | private T payload; 72 | 73 | private String responseCode; 74 | 75 | private String responseMessage; 76 | 77 | @Override 78 | public int getStatus() { 79 | return status; 80 | } 81 | 82 | @Override 83 | public boolean isSuccess() { 84 | return Result.STATUS_SUCCESS == status; 85 | } 86 | 87 | @Override 88 | public String getResponseCode() { 89 | return responseCode; 90 | } 91 | 92 | @Override 93 | public String getResponseMessage() { 94 | return responseMessage; 95 | } 96 | 97 | @Override 98 | public T getPayload() { 99 | return payload; 100 | } 101 | 102 | @Override 103 | public Map getHeaders() { 104 | return headers; 105 | } 106 | 107 | @Override 108 | public Result setHeader(String key, Object value) { 109 | headers.put(key, value); 110 | return this; 111 | } 112 | 113 | @Override 114 | public Object getHeader(String key) { 115 | return headers.get(key); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/support/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web.support; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | 5 | import org.apache.commons.logging.Log; 6 | import org.apache.commons.logging.LogFactory; 7 | import org.catframework.agileworking.BusinessException; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.ControllerAdvice; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | /** 15 | * 全局异常处理器,统一将异常转换为 {@link Result}. 16 | * 17 | * @author devzzm 18 | */ 19 | @ControllerAdvice 20 | public class GlobalExceptionHandler { 21 | 22 | private static final Log logger = LogFactory.getLog(GlobalExceptionHandler.class); 23 | 24 | @ExceptionHandler({ BusinessException.class, Exception.class }) 25 | @ResponseBody 26 | ResponseEntity handleControllerException(HttpServletRequest request, Throwable ex) { 27 | logger.error("handle exception:", ex); 28 | return new ResponseEntity<>(DefaultResult.newFailResult(ex), HttpStatus.INTERNAL_SERVER_ERROR); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/support/Result.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web.support; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 统一封装请求结果的接口,便于集中的进行异常处理以及便于调用方使用. 7 | * 8 | * @author devzzm 9 | */ 10 | public interface Result { 11 | 12 | /** 交易结果状态:成功. */ 13 | public static final int STATUS_SUCCESS = 0; 14 | 15 | /** 交易结果状态:失败. */ 16 | public static final int STATUS_FAIL = 1; 17 | 18 | /** 19 | * @return 此结果的状态 20 | * @see Result#STATUS_FAIL 21 | * @see Result#STATUS_SUCCESS 22 | */ 23 | int getStatus(); 24 | 25 | /** 26 | * @return 结果状态为 {@link Result#STATUS_SUCCESS} 返回 true 27 | */ 28 | boolean isSuccess(); 29 | 30 | /** 31 | * @return 响应码 32 | */ 33 | String getResponseCode(); 34 | 35 | /** 36 | * @return 响应消息 37 | */ 38 | String getResponseMessage(); 39 | 40 | /** 41 | * @return 消息所承载的数据 42 | */ 43 | T getPayload(); 44 | 45 | /** 46 | * @return 消息头承载的数据 47 | */ 48 | Map getHeaders(); 49 | 50 | /** 51 | * 向结果中添加 header 52 | * 53 | * @param key 添加 header 的 key 54 | * @param value 添加 header 的值 55 | * @return 当前结果对象 56 | */ 57 | Result setHeader(String key, Object value); 58 | 59 | /** 60 | * 取指定 key 的头的值 61 | * 62 | * @param key 取值的 key 63 | * @return header 的值 64 | */ 65 | Object getHeader(String key); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/catframework/agileworking/web/support/WebTokenHandlerInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web.support; 2 | 3 | import java.util.Arrays; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | import org.catframework.agileworking.service.WebTokenService; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.util.AntPathMatcher; 12 | import org.springframework.util.Assert; 13 | import org.springframework.util.PathMatcher; 14 | import org.springframework.util.StringUtils; 15 | import org.springframework.web.servlet.HandlerInterceptor; 16 | import org.springframework.web.servlet.ModelAndView; 17 | 18 | /** 19 | * 自定义拦截器实现了 WebToken 的校验. 20 | * 21 | * @author devzzm 22 | */ 23 | public class WebTokenHandlerInterceptor implements HandlerInterceptor { 24 | 25 | @Autowired 26 | private WebTokenService webTokenService; 27 | 28 | private String[] ignoreUriPatterns; 29 | 30 | private PathMatcher pathMatcher = new AntPathMatcher(); 31 | 32 | private String tokenKey = "Authorization"; 33 | 34 | private String subjectKey = "Subject"; 35 | 36 | @Override 37 | public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { 38 | if (isIgnoreUri(req.getRequestURI())) { 39 | return true; 40 | } else { 41 | String token = req.getHeader(tokenKey); 42 | Assert.notNull(token, "Authorization 不可为空."); 43 | String subject = req.getHeader(subjectKey); 44 | Assert.notNull(subject, "Subject 不可为空."); 45 | Assert.isTrue(webTokenService.verify(subject, token), "Web Token 校验失败."); 46 | return true; 47 | } 48 | } 49 | 50 | private boolean isIgnoreUri(String uri) { 51 | if (null == ignoreUriPatterns) 52 | return false; 53 | return Arrays.asList(ignoreUriPatterns).stream().anyMatch(p -> pathMatcher.match(p, uri)); 54 | } 55 | 56 | @Override 57 | public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception e) 58 | throws Exception { 59 | // do nothing 60 | } 61 | 62 | @Override 63 | public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView m) 64 | throws Exception { 65 | // do nothing 66 | } 67 | 68 | public void setWebTokenService(WebTokenService webTokenService) { 69 | this.webTokenService = webTokenService; 70 | } 71 | 72 | @Value("${web.token.ignore.uri.pattern}") 73 | public void setIgnoreUripattern(String ignoreUripattern) { 74 | if (!StringUtils.isEmpty(ignoreUripattern)) 75 | ignoreUriPatterns = StringUtils.tokenizeToStringArray(ignoreUripattern, ","); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | # profile 为单元测试时使用的配置 2 | spring.datasource.url=jdbc:mysql://119.29.92.158/agileworking_dev 3 | spring.datasource.username=agileworking_dev 4 | spring.datasource.password=iyah1984 5 | spring.jpa.hibernate.ddl-auto=create -------------------------------------------------------------------------------- /src/main/resources/application-prd.properties: -------------------------------------------------------------------------------- 1 | # profile 为服务器部署运行时使用的配置 2 | #spring.jpa.hibernate.ddl-auto=create 3 | spring.datasource.url=jdbc:mysql://localhost/agileworking 4 | spring.datasource.username=agileworking 5 | spring.datasource.password=iyah1984 -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | debug=true 2 | spring.profiles.active=prd 3 | ## Server Common Config 4 | server.context-path=/agileworking 5 | 6 | ## TimeZone Configuration 7 | spring.jackson.time-zone=GMT+8 8 | 9 | ## DataSource Common Config 10 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 11 | spring.datasource.max-idle=10 12 | spring.datasource.max-wait=10000 13 | spring.datasource.min-idle=5 14 | spring.datasource.initial-size=5 15 | spring.datasource.validation-query=SELECT 1 16 | spring.datasource.validation-interval=10000 17 | spring.datasource.time-between-eviction-runs-millis=10000 18 | spring.datasource.min-evictable-idle-time-millis=10000 19 | spring.datasource.remove-abandoned=true 20 | spring.datasource.remove-abandoned-timeout=60 21 | spring.datasource.test-on-borrow=true 22 | spring.datasource.test-while-idle=true 23 | 24 | ## JPA Common Configuration 25 | spring.jpa.open-in-view=true 26 | spring.jpa.show-sql=true 27 | spring.jpa.properties.hibernate.format_sql=true 28 | 29 | ## Web Token Configuration 30 | # 不需要进行 web token 的 uri 列表使用 ',' 分隔,支持 Ant-style 路径模式 31 | web.token.ignore.uri.pattern=/agileworking/teams,/agileworking/team/*/join,/agileworking/team/*/user/*,/agileworking/wechat/openid/*,/agileworking/schedules/* 32 | 33 | ## 消息提醒参数配置 34 | # 小程序应用 id 35 | wechat.app-id=wx4d8a2edf037d61e9 36 | # 小程序应用秘钥 37 | wechat.app-secret=bbd52b658d07b218be171f1e99f1b092 38 | # 小程序消息通知模板 id 39 | wechat.notify.template-id=NWlDmDjPmc_E-czrsxZQH1qr-LqrYsR9jRl4mJ_luQo 40 | wecaht.acquire-access-token-url=http://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret} 41 | wecaht.send-messag-url=http://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token={ACCESS_TOKEN} 42 | wecaht.jscode2session-url=http://api.weixin.qq.com/sns/jscode2session?appid={appId}&secret={appSecret}&js_code={jsCode}&grant_type=authorization_code -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | ${APP_LOG_PATH}/app.log 7 | 8 | app-%d{yyyy-MM-dd}.log 9 | 10 | 11 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 12 | 13 | 14 | 15 | 16 | 17 | 18 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/domain/MeetingRoomFactory.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * 用于单元测试的 {@link MeetingRoom} 工厂,毕竟在案例中繁琐的重复的构建初始化数据. 8 | * 9 | * @author devzzm 10 | */ 11 | public final class MeetingRoomFactory { 12 | 13 | public static final Long DEFAULT_TEAM_ID = 1L; 14 | 15 | public static final List defaultMeetingRooms() { 16 | List meetingRooms = new ArrayList<>(); 17 | meetingRooms.add(MeetingRoomFactory.newVideoMeetingRoom("3201", "大", "思科", "182.207.96.163", "21169")); 18 | meetingRooms.add(MeetingRoomFactory.newVideoMeetingRoom("3202", "中", "华为", "182.207.96.166", "120706")); 19 | meetingRooms.add(MeetingRoomFactory.newMeetingRoom("3203", "小")); 20 | meetingRooms.add(MeetingRoomFactory.newMeetingRoom("3206", "小")); 21 | return meetingRooms; 22 | } 23 | 24 | public static final MeetingRoom newVideoMeetingRoom(String roomNo, String size, String vendor, String ip, 25 | String terminalIp) { 26 | MeetingRoom meetingRoom = new MeetingRoom(); 27 | meetingRoom.setRoomNo(roomNo); 28 | meetingRoom.setIp(ip); 29 | meetingRoom.setSize(size); 30 | meetingRoom.setTerminalId(terminalIp); 31 | meetingRoom.setType("视频"); 32 | meetingRoom.setVnedor(vendor); 33 | meetingRoom.setTeamId(MeetingRoomFactory.DEFAULT_TEAM_ID); 34 | return meetingRoom; 35 | } 36 | 37 | public static final MeetingRoom newMeetingRoom(String roomNo, String size) { 38 | MeetingRoom meetingRoom = new MeetingRoom(); 39 | meetingRoom.setRoomNo(roomNo); 40 | meetingRoom.setSize(size); 41 | meetingRoom.setType("普通"); 42 | meetingRoom.setTeamId(MeetingRoomFactory.DEFAULT_TEAM_ID); 43 | return meetingRoom; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/domain/ScheduleFactory.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import org.catframework.agileworking.utils.DateUtils; 4 | 5 | public final class ScheduleFactory { 6 | 7 | public static final String DEFAULT_CREATOR_AVATAR_URL = "http://baidu.com"; 8 | 9 | public static Schedule newSchedule(String title, String creator, String date, String startTime, String endTime) { 10 | Schedule s = new Schedule(); 11 | s.setTitle(title); 12 | s.setCreatorNickName(creator); 13 | s.setCreatorOpenId(creator); 14 | s.setCreatorAvatarUrl(ScheduleFactory.DEFAULT_CREATOR_AVATAR_URL); 15 | s.setDate(DateUtils.parse(date, DateUtils.PATTERN_SIMPLE_DATE)); 16 | s.setStartTime(startTime); 17 | s.setEndTime(endTime); 18 | s.setRepeatMode(Schedule.REPEAT_MODE_NO); 19 | return s; 20 | } 21 | 22 | public static Schedule newWeeklySchedule(String title, String creator, String date, String startTime, 23 | String endTime) { 24 | Schedule s = newSchedule(title, creator, date, startTime, endTime); 25 | s.setRepeatMode(Schedule.REPEAT_MODE_WEEKLY); 26 | return s; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/domain/ScheduleRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import java.util.List; 4 | 5 | import org.catframework.agileworking.utils.DateUtils; 6 | import org.catframework.agileworking.vo.ScheduleVO; 7 | import org.catframework.agileworking.web.MeetingRoomController; 8 | import org.catframework.agileworking.web.ScheduleController; 9 | import org.catframework.agileworking.web.support.Result; 10 | import org.junit.After; 11 | import org.junit.Assert; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest 21 | public class ScheduleRepositoryTest { 22 | 23 | @Autowired 24 | private ScheduleRepository scheduleRepository; 25 | 26 | @Autowired 27 | private ScheduleController scheduleController; 28 | 29 | @Autowired 30 | private MeetingRoomController meetingRoomController; 31 | 32 | @Autowired 33 | private MeetingRoomRepository meetingRoomRepository; 34 | 35 | 36 | @Autowired 37 | private TeamRepository teamRepository; 38 | 39 | @Autowired 40 | private UserRepository userRepository; 41 | 42 | private Team team = TeamFactory.newDefaultTeam(); 43 | 44 | private User user = UserFactory.newDefaultUser(); 45 | 46 | private List meetingRooms = MeetingRoomFactory.defaultMeetingRooms(); 47 | 48 | @Before 49 | public void before() { 50 | userRepository.save(user); 51 | teamRepository.save(team); 52 | team.addUser(user); 53 | teamRepository.save(team); 54 | meetingRooms.stream().forEach(m-> m.setTeamId(team.getId())); 55 | meetingRoomRepository.save(meetingRooms); 56 | } 57 | 58 | @After 59 | public void after() { 60 | teamRepository.delete(team.getId()); 61 | userRepository.delete(user); 62 | meetingRoomRepository.delete(meetingRooms); 63 | } 64 | 65 | @Test 66 | public void testFindScheules() { 67 | 68 | Result> result = meetingRoomController.list(MeetingRoomFactory.DEFAULT_TEAM_ID); 69 | Schedule s = ScheduleFactory.newWeeklySchedule("分行业务平台项目组临时会议", "七猫", "2017-08-02", "13:00", "14:00"); 70 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(),"fakeFormId", s); 71 | Participant p = new Participant(); 72 | p.setDate(DateUtils.parse("2017-08-02", DateUtils.PATTERN_SIMPLE_DATE)); 73 | p.setAvatarUrl("some url"); 74 | p.setNickName("7upcat"); 75 | p.setOpenId("7upcat_open_id"); 76 | scheduleController.join(s.getId(), p); 77 | s = scheduleRepository.findOne(s.getId()); 78 | Assert.assertEquals(2, s.getParticipants().size()); 79 | Assert.assertEquals("七猫", s.getParticipants().get(0).getNickName()); 80 | try { 81 | scheduleController.join(s.getId(), p); 82 | Assert.fail(); 83 | } catch (Exception e) { 84 | Assert.assertEquals("您已加入过此会议啦.", e.getMessage()); 85 | } 86 | List scheduleVOs=scheduleRepository.findByOpenIdAndDate("7upcat_open_id",DateUtils.parse("2017-08-09", DateUtils.PATTERN_SIMPLE_DATE)); 87 | Assert.assertEquals(1, scheduleVOs.size()); 88 | scheduleRepository.delete(s); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/domain/ScheduleTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | import static org.junit.Assert.assertSame; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | 13 | public class ScheduleTest { 14 | 15 | @Test 16 | public void testCompareTo() { 17 | Schedule s1 = ScheduleFactory.newSchedule("XXX1", "发哥", "2017-08-02", "13:30", "15:00"); 18 | Schedule s2 = ScheduleFactory.newSchedule("XXX2", "发哥", "2017-08-03", "13:30", "15:00"); 19 | assertTrue(s1.compareTo(s2) < 0); 20 | Schedule s3 = ScheduleFactory.newSchedule("XXX2", "发哥", "2017-08-02", "16:30", "17:00"); 21 | assertTrue(s1.compareTo(s3) < 0); 22 | List schedules = new ArrayList<>(); 23 | schedules.add(s3); 24 | schedules.add(s2); 25 | schedules.add(s1); 26 | schedules = schedules.stream().sorted().collect(Collectors.toList()); 27 | assertSame(s1, schedules.get(0)); 28 | assertSame(s3, schedules.get(1)); 29 | assertSame(s2, schedules.get(2)); 30 | } 31 | 32 | @Test 33 | public void isConflict() { 34 | Schedule s1 = ScheduleFactory.newSchedule("XXX1", "发哥", "2017-08-02", "13:30", "15:00"); 35 | s1.setId((long) 1); 36 | Schedule s2 = ScheduleFactory.newSchedule("XXX2", "发哥", "2017-08-03", "13:30", "15:00"); 37 | s2.setId((long) 2); 38 | Assert.assertFalse(s1.isConflict(s2)); 39 | Schedule s3 = ScheduleFactory.newSchedule("XXX2", "发哥", "2017-08-02", "13:30", "15:00"); 40 | s3.setId((long) 3); 41 | Assert.assertTrue(s1.isConflict(s3)); 42 | Schedule s4 = ScheduleFactory.newSchedule("XXX2", "发哥", "2017-08-02", "10:00", "16:00"); 43 | s4.setId((long) 4); 44 | Assert.assertTrue(s1.isConflict(s4)); 45 | Schedule s5 = ScheduleFactory.newSchedule("XXX2", "发哥", "2017-08-02", "10:00", "13:30"); 46 | s5.setId((long) 5); 47 | Assert.assertFalse(s1.isConflict(s5)); 48 | Schedule s6 = ScheduleFactory.newSchedule("XXX2", "发哥", "2017-08-02", "15:00", "17:00"); 49 | s6.setId((long) 6); 50 | Assert.assertFalse(s1.isConflict(s6)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/domain/TeamFactory.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | public final class TeamFactory { 4 | 5 | /** 团队注册时使用的默认的口令. */ 6 | public static final String DEFAULT_TEAM_TOKEN = "9527"; 7 | 8 | public static Team newDefaultTeam() { 9 | Team team = new Team(); 10 | team.setName("深研二部"); 11 | team.setDesc("一个敏捷的团队"); 12 | team.setToken(TeamFactory.DEFAULT_TEAM_TOKEN); 13 | return team; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/domain/UserFactory.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.domain; 2 | 3 | public final class UserFactory { 4 | 5 | public static User newDefaultUser() { 6 | User user = new User(); 7 | user.setOpenId("7upcat_open_id"); 8 | user.setAvatarUrl("http://catframework.cn"); 9 | user.setMobileNo("18603010499"); 10 | user.setName("郑明明"); 11 | user.setNickName("7upcat"); 12 | return user; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author devzzm 3 | */ 4 | package org.catframework.agileworking; -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/scheduling/SendNotifyMessageJobTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.scheduling; 2 | 3 | import java.util.Date; 4 | 5 | import org.catframework.agileworking.utils.DateUtils; 6 | import org.junit.Test; 7 | 8 | public class SendNotifyMessageJobTest { 9 | 10 | @Test 11 | public void testIsNeedSendMessageNow() { 12 | Date d =DateUtils.parse("2017-08-31" + " " + "10:20",DateUtils.PATTERN_SIMPLE_DATE + " HH:mm"); 13 | Long time =d.getTime()+15 * 60 * 1000; 14 | System.out.println(new Date(time)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/service/impl/ScheduleServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.service.impl; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.text.DateFormat; 6 | import java.text.ParseException; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Calendar; 9 | import java.util.List; 10 | 11 | import org.catframework.agileworking.domain.MeetingRoom; 12 | import org.catframework.agileworking.domain.MeetingRoomFactory; 13 | import org.catframework.agileworking.domain.MeetingRoomRepository; 14 | import org.catframework.agileworking.domain.Schedule; 15 | import org.catframework.agileworking.domain.ScheduleFactory; 16 | import org.catframework.agileworking.domain.ScheduleRepository; 17 | import org.catframework.agileworking.domain.User; 18 | import org.catframework.agileworking.domain.UserFactory; 19 | import org.catframework.agileworking.domain.UserRepository; 20 | import org.catframework.agileworking.utils.DateUtils; 21 | import org.catframework.agileworking.web.MeetingRoomController; 22 | import org.catframework.agileworking.web.support.Result; 23 | import org.junit.After; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.springframework.beans.factory.annotation.Autowired; 28 | import org.springframework.boot.test.context.SpringBootTest; 29 | import org.springframework.test.context.junit4.SpringRunner; 30 | 31 | @RunWith(SpringRunner.class) 32 | @SpringBootTest 33 | public class ScheduleServiceImplTest { 34 | 35 | @Autowired 36 | private MeetingRoomController meetingRoomController; 37 | 38 | @Autowired 39 | private MeetingRoomRepository meetingRoomRepository; 40 | 41 | @Autowired 42 | private ScheduleRepository scheduleRepository; 43 | 44 | @Autowired 45 | private UserRepository userRepository; 46 | 47 | private List meetingRooms = MeetingRoomFactory.defaultMeetingRooms(); 48 | 49 | private User user = UserFactory.newDefaultUser(); 50 | 51 | @Before 52 | public void before() { 53 | user.setOpenId("七猫"); 54 | userRepository.save(user); 55 | meetingRoomRepository.save(meetingRooms); 56 | } 57 | 58 | @After 59 | public void after() { 60 | meetingRoomRepository.delete(meetingRooms); 61 | userRepository.delete(user); 62 | } 63 | 64 | @Test 65 | public void testCalendar() throws ParseException { 66 | Calendar cal = Calendar.getInstance(); 67 | DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 68 | cal.setTime(dateFormat.parse("2017-08-01")); 69 | assertEquals(Calendar.TUESDAY, cal.get(Calendar.DAY_OF_WEEK)); 70 | } 71 | 72 | @Test 73 | public void testFind() { 74 | Schedule s1 = ScheduleFactory.newSchedule("分行业务平台项目组临时会议", "七猫", "2017-08-09", "09:00", "12:00"); 75 | Schedule s2 = ScheduleFactory.newWeeklySchedule("分行业务平台项目组临时会议", "七猫", "2017-08-02", "12:00", "14:00"); 76 | Schedule s3 = ScheduleFactory.newSchedule("分行业务平台项目组临时会议", "七猫", "2017-08-16", "14:00", "16:00"); 77 | try { 78 | meetingRoomController.createOrUpdateSchedule(meetingRooms.get(0).getId(),"fakeFormId" ,s1); 79 | meetingRoomController.createOrUpdateSchedule(meetingRooms.get(0).getId(),"fakeFormId",s2); 80 | meetingRoomController.createOrUpdateSchedule(meetingRooms.get(0).getId(),"fakeFormId",s3); 81 | Result> result = meetingRoomController.schedules(meetingRooms.get(0).getId(), 82 | DateUtils.parse("2017-08-09", DateUtils.PATTERN_SIMPLE_DATE)); 83 | assertEquals(2, result.getPayload().size()); 84 | } finally { 85 | scheduleRepository.delete(s1); 86 | scheduleRepository.delete(s2); 87 | scheduleRepository.delete(s3); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/service/impl/SimpleJJWTWebTokenServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.service.impl; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.catframework.agileworking.service.WebTokenService; 6 | import org.junit.Test; 7 | 8 | public class SimpleJJWTWebTokenServiceImplTest { 9 | 10 | private static WebTokenService webTokenService = new SimpleJJWTWebTokenServiceImpl(); 11 | 12 | private static String token = null; 13 | 14 | private static String subject = "7upcat"; 15 | 16 | @Test 17 | public void testGenerate() { 18 | token = webTokenService.generate(subject); 19 | assertNotNull(token); 20 | } 21 | 22 | @Test 23 | public void testVerify() { 24 | assertTrue(webTokenService.verify(subject, token)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/service/impl/WeChatApiIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.service.impl; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.apache.commons.logging.Log; 7 | import org.apache.commons.logging.LogFactory; 8 | import org.catframework.agileworking.utils.JsonUtils; 9 | import org.junit.Assert; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.web.client.RestTemplate; 17 | 18 | /** 19 | * 集成测试验证,需要提供 form_id 才可以进行测试. 20 | * @author devzzm 21 | */ 22 | @RunWith(SpringRunner.class) 23 | @SpringBootTest 24 | public class WeChatApiIntegrationTest { 25 | 26 | private static final Log logger = LogFactory.getLog(WeChatApiIntegrationTest.class); 27 | 28 | @Value("${wechat.app-id}") 29 | private String appId; 30 | 31 | @Value("${wechat.app-secret}") 32 | private String appSecret; 33 | 34 | @Value("${wechat.notify.template-id}") 35 | private String templateId; 36 | 37 | private String accessToken; 38 | 39 | private RestTemplate restTemplate = new RestTemplate(); 40 | 41 | @Before 42 | public void init() { 43 | String result = restTemplate.getForObject( 44 | "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}", 45 | String.class, appId, appSecret); 46 | accessToken = (String) JsonUtils.decode(result).get("access_token"); 47 | Assert.assertNotNull(accessToken); 48 | } 49 | 50 | @Test 51 | public void testSendMessage() { 52 | Map request = new HashMap<>(); 53 | // TODO 目标用户的 openId 需要按实际指定 54 | request.put("touser", "oFb4O0XMZrijTGa_5kli4E6shEV0"); 55 | request.put("template_id", templateId); 56 | // TODO 后续验证需要提供 from_id才可以进行 57 | request.put("form_id", "1504144250621"); 58 | Map data = new HashMap<>(); 59 | Map keyword1 = new HashMap<>(); 60 | keyword1.put("value", "11111"); 61 | data.put("keyword1", keyword1); 62 | 63 | Map keyword2 = new HashMap<>(); 64 | keyword2.put("value", "22222"); 65 | data.put("keyword2", keyword2); 66 | 67 | Map keyword3 = new HashMap<>(); 68 | keyword3.put("value", "33333"); 69 | data.put("keyword3", keyword3); 70 | 71 | Map keyword4 = new HashMap<>(); 72 | keyword4.put("value", "44444"); 73 | data.put("keyword4", keyword4); 74 | 75 | Map keyword5 = new HashMap<>(); 76 | keyword5.put("value", "5555"); 77 | data.put("keyword5", keyword5); 78 | request.put("data", data); 79 | String result = restTemplate.postForObject( 80 | "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token={ACCESS_TOKEN}", request, 81 | String.class, accessToken); 82 | logger.info(result); 83 | } 84 | 85 | 86 | public static void main(String[] args) { 87 | Map data = new HashMap<>(); 88 | data.put("REQUEST_MESSAGE", "2222"); 89 | new RestTemplate().postForObject( 90 | "http://localhost:8080/MOBX-ANTD-ADMIN/loginProcess.json?REQUEST_MESSAGE=2222", data,String.class); 91 | } 92 | } 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/utils/DateUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.utils; 2 | 3 | import java.util.Date; 4 | 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | public class DateUtilsTest { 9 | 10 | @Test 11 | public void testFormat() { 12 | Date date = DateUtils.parse("2017-08-31", DateUtils.PATTERN_SIMPLE_DATE); 13 | Assert.assertEquals("2017年08月31日",DateUtils.format(date, "yyyy年MM月dd日")); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/utils/JsonUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.utils; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.util.Map; 6 | 7 | import org.junit.Test; 8 | 9 | public class JsonUtilsTest { 10 | 11 | @Test 12 | public void testDecode() { 13 | Map result =JsonUtils.decode("{\"access_token\":\"ExV9F-XgAZR3uLnUv269VoEuCkeSoSISWigFuwYHw3zlQMKqNn88hW4tsQbp-Ie46oI3MIKYBeKm1nsCO4qBr_PiyC4dqkpqBMgI0n8buVhHBZC4qrsYDqf8Zb2GxQdUMPTgAJAILP\",\"expires_in\":7200}"); 14 | assertEquals(7200,result.get("expires_in")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/vo/NotifyMessageTemplateTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.vo; 2 | 3 | import java.util.Map; 4 | 5 | import org.catframework.agileworking.domain.MeetingRoom; 6 | import org.catframework.agileworking.domain.Participant; 7 | import org.catframework.agileworking.domain.Schedule; 8 | import org.catframework.agileworking.domain.User; 9 | import org.catframework.agileworking.utils.DateUtils; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | 13 | public class NotifyMessageTemplateTest { 14 | 15 | @SuppressWarnings("unchecked") 16 | @Test 17 | public void testToTemplateMessage() { 18 | User creator = new User(); 19 | creator.setOpenId("xxOpenId"); 20 | creator.setName("郑明明"); 21 | creator.setNickName("七猫"); 22 | creator.setMobileNo("18603010498"); 23 | MeetingRoom meetingRoom = new MeetingRoom(); 24 | meetingRoom.setRoomNo("3203"); 25 | Schedule schedule = new Schedule(); 26 | schedule.setTitle("讨论一下明天吃点咩"); 27 | schedule.setMeetingRoom(meetingRoom); 28 | schedule.setDate(DateUtils.parse("2017-08-31", DateUtils.PATTERN_SIMPLE_DATE)); 29 | schedule.setStartTime("10:00"); 30 | schedule.setEndTime("12:00"); 31 | Participant participant = new Participant(); 32 | participant.setNickName("七猫"); 33 | participant.setOpenId(creator.getOpenId()); 34 | participant.setFormId("mockFormId"); 35 | schedule.addParticipant(participant); 36 | NotifyMessageTemplate teamplate = new NotifyMessageTemplate(creator, schedule, participant, "mockTemplateId"); 37 | Map message = teamplate.toTemplateMessage(); 38 | 39 | Assert.assertEquals("xxOpenId", message.get("touser")); 40 | Assert.assertEquals("mockTemplateId", message.get("template_id")); 41 | Assert.assertEquals("mockFormId", message.get("form_id")); 42 | Map data = (Map) message.get("data"); 43 | Map keyword1 = (Map) data.get("keyword1"); 44 | Assert.assertEquals(schedule.getTitle(), keyword1.get("value")); 45 | Map keyword2 = (Map) data.get("keyword2"); 46 | Assert.assertEquals("2017年08月31日 10:00-12:00", keyword2.get("value")); 47 | Map keyword3 = (Map) data.get("keyword3"); 48 | Assert.assertEquals(meetingRoom.getRoomNo(), keyword3.get("value")); 49 | Map keyword4 = (Map) data.get("keyword4"); 50 | Assert.assertEquals("1人参加", keyword4.get("value")); 51 | Map keyword5 = (Map) data.get("keyword5"); 52 | Assert.assertEquals(creator.getNickName()+"/"+creator.getName()+"("+creator.getMobileNo()+")", keyword5.get("value")); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/web/MeetingRoomControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import org.catframework.agileworking.domain.ScheduleFactory; 4 | import org.junit.Test; 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | public class MeetingRoomControllerIntegrationTest { 8 | 9 | private String url = "http://localhost:8080"; 10 | 11 | @Test 12 | public void schedules() { 13 | RestTemplate restTemplate= new RestTemplate(); 14 | restTemplate.postForObject(url+"/meetingRooms/{id}/schedule", ScheduleFactory.newSchedule("测试排期", "7upcat", "2017-02-02", "12:00", "16:00"), String.class,"1"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/web/MeetingRoomControllerTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import java.util.List; 4 | 5 | import org.catframework.agileworking.domain.MeetingRoom; 6 | import org.catframework.agileworking.domain.MeetingRoomFactory; 7 | import org.catframework.agileworking.domain.MeetingRoomRepository; 8 | import org.catframework.agileworking.domain.Schedule; 9 | import org.catframework.agileworking.domain.ScheduleFactory; 10 | import org.catframework.agileworking.domain.ScheduleRepository; 11 | import org.catframework.agileworking.domain.User; 12 | import org.catframework.agileworking.domain.UserFactory; 13 | import org.catframework.agileworking.domain.UserRepository; 14 | import org.catframework.agileworking.utils.DateUtils; 15 | import org.catframework.agileworking.web.support.Result; 16 | import org.junit.After; 17 | import org.junit.Assert; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.boot.test.context.SpringBootTest; 23 | import org.springframework.test.context.junit4.SpringRunner; 24 | 25 | @RunWith(SpringRunner.class) 26 | @SpringBootTest 27 | public class MeetingRoomControllerTest { 28 | 29 | @Autowired 30 | private MeetingRoomController meetingRoomController; 31 | 32 | @Autowired 33 | private MeetingRoomRepository meetingRoomRepository; 34 | 35 | @Autowired 36 | private ScheduleRepository scheduleRepository; 37 | 38 | @Autowired 39 | private UserRepository userRepository; 40 | 41 | private User user1 = UserFactory.newDefaultUser(); 42 | private User user2 = UserFactory.newDefaultUser(); 43 | 44 | private List meetingRooms = MeetingRoomFactory.defaultMeetingRooms(); 45 | 46 | @Before 47 | public void before() { 48 | user1.setOpenId("七猫"); 49 | user2.setOpenId("发哥"); 50 | userRepository.save(user1); 51 | userRepository.save(user2); 52 | meetingRoomRepository.save(meetingRooms); 53 | } 54 | 55 | @After 56 | public void after() { 57 | meetingRoomRepository.delete(meetingRooms); 58 | userRepository.delete(user1); 59 | userRepository.delete(user2); 60 | 61 | } 62 | 63 | @Test 64 | public void list() throws Exception { 65 | Result> result = meetingRoomController.list(MeetingRoomFactory.DEFAULT_TEAM_ID); 66 | Assert.assertTrue(result.isSuccess()); 67 | org.junit.Assert.assertEquals(4, result.getPayload().size()); 68 | } 69 | 70 | @Test 71 | public void newSchedule() { 72 | Result> result = meetingRoomController.list(MeetingRoomFactory.DEFAULT_TEAM_ID); 73 | 74 | // 创建一个每周重复的排期 75 | Schedule schedule1 = ScheduleFactory.newWeeklySchedule("分行业务平台项目组临时会议", "七猫", "2017-08-02", "09:00", "12:00"); 76 | try { 77 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), "fakeFormId", schedule1); 78 | Assert.assertNotNull(schedule1.getId()); 79 | // 案例验证更新排期 80 | schedule1.setTitle("分行业务平台项目组临时会议-修订后"); 81 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), "fakeFormId", schedule1); 82 | Schedule dbSchedule = scheduleRepository.findOne(schedule1.getId()); 83 | Assert.assertEquals(schedule1.getTitle(), dbSchedule.getTitle()); 84 | // 创建人默人参加会议 85 | Assert.assertEquals(1, dbSchedule.getParticipants().size()); 86 | Assert.assertEquals(schedule1.getCreatorAvatarUrl(), dbSchedule.getParticipants().get(0).getAvatarUrl()); 87 | 88 | // 当天时间有冲突的案例 89 | Schedule schedule2 = ScheduleFactory.newSchedule("POS清算代码评审", "发哥", "2017-08-02", "10:00", "11:00"); 90 | try { 91 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), "fakeFormId", 92 | schedule2); 93 | Assert.fail(); 94 | } catch (Exception e) { 95 | Assert.assertEquals("同已有排期冲突.", e.getMessage()); 96 | } 97 | 98 | // 下周时间有冲突的案例 99 | Schedule schedule3 = ScheduleFactory.newSchedule("POS清算代码评审", "发哥", "2017-08-09", "09:30", "15:00"); 100 | try { 101 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), "fakeFormId", 102 | schedule3); 103 | Assert.fail(); 104 | } catch (Exception e) { 105 | Assert.assertEquals("同已有排期冲突.", e.getMessage()); 106 | } 107 | } finally { 108 | scheduleRepository.delete(schedule1); 109 | } 110 | 111 | } 112 | 113 | @Test 114 | public void cancelSchedule() { 115 | Result> result = meetingRoomController.list(MeetingRoomFactory.DEFAULT_TEAM_ID); 116 | // 创建一个每周重复的排期 117 | Schedule s = ScheduleFactory.newWeeklySchedule("分行业务平台项目组临时会议", "七猫", "2017-08-02", "13:00", "14:00"); 118 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), "fakeFormId", s); 119 | Assert.assertNotNull(scheduleRepository.findOne(s.getId())); 120 | meetingRoomController.cancelSchedule(s.getId()); 121 | Assert.assertNull(scheduleRepository.findOne(s.getId())); 122 | } 123 | 124 | @Test 125 | public void schedules() { 126 | Result> result = meetingRoomController.list(MeetingRoomFactory.DEFAULT_TEAM_ID); 127 | // 创建一个每周重复的排期 128 | Schedule schedule1 = ScheduleFactory.newWeeklySchedule("分行业务平台项目组临时会议", "七猫", "2017-08-02", "13:00", "14:00"); 129 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), "fakeFormId", schedule1); 130 | Schedule schedule2 = ScheduleFactory.newWeeklySchedule("CPOS临时例会", "发哥", "2017-08-09", "14:00", "16:00"); 131 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), "fakeFormId", schedule2); 132 | try { 133 | Result> schedulesResult = meetingRoomController.schedules(result.getPayload().get(0).getId(), 134 | DateUtils.parse("2017-08-09", DateUtils.PATTERN_SIMPLE_DATE)); 135 | Assert.assertEquals(2, schedulesResult.getPayload().size()); 136 | } finally { 137 | scheduleRepository.delete(schedule1); 138 | scheduleRepository.delete(schedule2); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/web/ScheduleControllerTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import java.util.List; 4 | 5 | import org.catframework.agileworking.domain.MeetingRoom; 6 | import org.catframework.agileworking.domain.MeetingRoomFactory; 7 | import org.catframework.agileworking.domain.MeetingRoomRepository; 8 | import org.catframework.agileworking.domain.Participant; 9 | import org.catframework.agileworking.domain.Schedule; 10 | import org.catframework.agileworking.domain.ScheduleFactory; 11 | import org.catframework.agileworking.domain.ScheduleRepository; 12 | import org.catframework.agileworking.domain.Team; 13 | import org.catframework.agileworking.domain.TeamFactory; 14 | import org.catframework.agileworking.domain.TeamRepository; 15 | import org.catframework.agileworking.domain.User; 16 | import org.catframework.agileworking.domain.UserFactory; 17 | import org.catframework.agileworking.domain.UserRepository; 18 | import org.catframework.agileworking.web.support.Result; 19 | import org.junit.After; 20 | import org.junit.Assert; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.boot.test.context.SpringBootTest; 26 | import org.springframework.test.context.junit4.SpringRunner; 27 | 28 | @RunWith(SpringRunner.class) 29 | @SpringBootTest 30 | public class ScheduleControllerTest { 31 | 32 | @Autowired 33 | private ScheduleController scheduleController; 34 | 35 | @Autowired 36 | private MeetingRoomController meetingRoomController; 37 | 38 | @Autowired 39 | private ScheduleRepository scheduleRepository; 40 | 41 | @Autowired 42 | private MeetingRoomRepository meetingRoomRepository; 43 | 44 | @Autowired 45 | private TeamRepository teamRepository; 46 | 47 | @Autowired 48 | private UserRepository userRepository; 49 | 50 | private Team team = TeamFactory.newDefaultTeam(); 51 | 52 | private User user = UserFactory.newDefaultUser(); 53 | 54 | private List meetingRooms = MeetingRoomFactory.defaultMeetingRooms(); 55 | 56 | @Before 57 | public void before() { 58 | userRepository.save(user); 59 | teamRepository.save(team); 60 | team.addUser(user); 61 | teamRepository.save(team); 62 | meetingRooms.stream().forEach(m -> m.setTeamId(team.getId())); 63 | meetingRoomRepository.save(meetingRooms); 64 | } 65 | 66 | @After 67 | public void after() { 68 | teamRepository.delete(team.getId()); 69 | userRepository.delete(user); 70 | meetingRoomRepository.delete(meetingRooms); 71 | } 72 | 73 | @Test 74 | public void testJoin() { 75 | Result> result = meetingRoomController.list(team.getId()); 76 | Schedule s = ScheduleFactory.newWeeklySchedule("分行业务平台项目组临时会议", "七猫", "2017-08-02", "13:00", "14:00"); 77 | meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), "fakeFormId", s); 78 | Participant p = new Participant(); 79 | p.setAvatarUrl("some url"); 80 | p.setNickName("7upcat"); 81 | p.setOpenId("7upcat_open_id"); 82 | p.setFormId("fakeFormId"); 83 | scheduleController.join(s.getId(), p); 84 | s = scheduleRepository.findOne(s.getId()); 85 | Assert.assertEquals(2, s.getParticipants().size()); 86 | Assert.assertEquals("七猫", s.getParticipants().get(0).getNickName()); 87 | Assert.assertEquals("7upcat", s.getParticipants().get(1).getNickName()); 88 | Assert.assertEquals("fakeFormId", s.getParticipants().get(1).getFormId()); 89 | try { 90 | scheduleController.join(s.getId(), p); 91 | Assert.fail(); 92 | } catch (Exception e) { 93 | Assert.assertEquals("您已加入过此会议啦.", e.getMessage()); 94 | } 95 | scheduleRepository.delete(s); 96 | } 97 | 98 | @Test 99 | public void testGet() { 100 | Result> result = meetingRoomController.list(team.getId()); 101 | Schedule s = ScheduleFactory.newWeeklySchedule("分行业务平台项目组临时会议", "7upcat_open_id", "2017-08-02", "13:00", "14:00"); 102 | Result sResult = meetingRoomController.createOrUpdateSchedule(result.getPayload().get(0).getId(), 103 | "fakeFormId", s); 104 | Assert.assertTrue(sResult.isSuccess()); 105 | sResult = scheduleController.get(sResult.getPayload().getId()); 106 | Assert.assertNotNull(sResult); 107 | scheduleRepository.delete(s); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/web/TeamControllerTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import java.util.List; 4 | 5 | import org.catframework.agileworking.domain.Team; 6 | import org.catframework.agileworking.domain.TeamFactory; 7 | import org.catframework.agileworking.domain.TeamRepository; 8 | import org.catframework.agileworking.domain.User; 9 | import org.catframework.agileworking.domain.UserFactory; 10 | import org.catframework.agileworking.domain.UserRepository; 11 | import org.catframework.agileworking.service.WebTokenService; 12 | import org.catframework.agileworking.web.support.Result; 13 | import org.junit.Assert; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.test.context.junit4.SpringRunner; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | public class TeamControllerTest { 23 | 24 | @Autowired 25 | private TeamRepository teamRepository; 26 | 27 | @Autowired 28 | private TeamController teamController; 29 | 30 | @Autowired 31 | private UserRepository userRepository; 32 | 33 | @Autowired 34 | private WebTokenService webTokenService; 35 | 36 | @Test 37 | public void testJoin() { 38 | Team team = teamRepository.save(TeamFactory.newDefaultTeam()); 39 | User user = UserFactory.newDefaultUser(); 40 | userRepository.save(user); 41 | team = teamRepository.findOne(team.getId()); 42 | Result result = teamController.join(team.getId(), user, team.getToken()); 43 | String token = (String) result.getHeader("token"); 44 | Assert.assertEquals(webTokenService.generate(user.getOpenId()), token); 45 | team = teamRepository.findOne(team.getId()); 46 | Assert.assertEquals(1, team.getUsers().size()); 47 | team.getUsers().clear(); 48 | teamRepository.save(team); 49 | team = teamRepository.findOne(team.getId()); 50 | Assert.assertEquals(0, team.getUsers().size()); 51 | userRepository.delete(user); 52 | teamRepository.delete(team); 53 | } 54 | 55 | @Test 56 | public void testList() { 57 | Team team = teamRepository.save(TeamFactory.newDefaultTeam()); 58 | Result> result = teamController.findAll(); 59 | Assert.assertEquals(1, result.getPayload().size()); 60 | teamRepository.delete(team); 61 | } 62 | 63 | @Test 64 | public void testGetUser() { 65 | Team team = teamRepository.save(TeamFactory.newDefaultTeam()); 66 | User user = UserFactory.newDefaultUser(); 67 | userRepository.save(user); 68 | Result result = teamController.getUser(team.getId(), user.getOpenId()); 69 | Assert.assertFalse(result.isSuccess()); 70 | Assert.assertEquals("用户未绑定.", result.getResponseMessage()); 71 | teamController.join(team.getId(), user, team.getToken()); 72 | result = teamController.getUser(team.getId(), user.getOpenId()); 73 | Assert.assertTrue(result.isSuccess()); 74 | String token = (String) result.getHeader("token"); 75 | Assert.assertEquals(webTokenService.generate(user.getOpenId()), token); 76 | userRepository.delete(user); 77 | teamRepository.delete(team); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/org/catframework/agileworking/web/WechatControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.catframework.agileworking.web; 2 | 3 | import org.apache.commons.logging.Log; 4 | import org.apache.commons.logging.LogFactory; 5 | import org.catframework.agileworking.web.support.Result; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | /** 13 | * 集成测试获取 openid ,需要指定 jsCode 14 | * @author devzzm 15 | */ 16 | @RunWith(SpringRunner.class) 17 | @SpringBootTest 18 | public class WechatControllerIntegrationTest { 19 | 20 | private static final Log logger = LogFactory.getLog(WechatControllerIntegrationTest.class); 21 | 22 | @Autowired 23 | private WechatController wechatController; 24 | 25 | @Test 26 | public void test() { 27 | String jsCode = "someJsCode"; 28 | Result result = wechatController.getOpenId(jsCode); 29 | logger.info(result.getPayload()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------