├── settings.gradle ├── pic ├── gitlab_closed.png ├── gitlab_merged.png ├── gitlab_reopen.png ├── gitlab_open_msg.png ├── dignding_contact.png ├── jira_issue_create.png ├── jira_issue_update.png └── pgyer_new_version.png ├── src └── main │ ├── kotlin │ └── org │ │ └── lynxz │ │ └── server │ │ ├── config │ │ ├── MessageType.java │ │ ├── Actions.kt │ │ ├── PathInfo.kt │ │ ├── KeyNames.kt │ │ └── ConstantsPara.kt │ │ ├── service │ │ ├── ActionService.kt │ │ ├── PlatformService.kt │ │ ├── PersistenceService.kt │ │ ├── PgyerService.kt │ │ ├── JenkinsService.kt │ │ ├── SendMessageService.kt │ │ ├── GiraService.kt │ │ └── GitlabService.kt │ │ ├── util │ │ ├── DateUtil.kt │ │ └── CommonUtil.kt │ │ ├── bean │ │ ├── AccessTokenBean.kt │ │ ├── PersistenceDataBean.kt │ │ ├── SendMessageReqBean.kt │ │ ├── TgSendMessageReqBean.kt │ │ ├── PgyerUpdateHookBean.kt │ │ ├── DepartmentMemberDetailListBean.kt │ │ ├── MessageTextBean.java │ │ ├── TgSendMessageRespBean.kt │ │ ├── MessageResponseBean.java │ │ ├── DepartmentListBean.java │ │ ├── JenkensUploadPygerBean.kt │ │ ├── TgGetUpdateResponseBean.kt │ │ ├── GbPushRequestEventBean.java │ │ └── GbMergeRequestEventBean.java │ │ ├── Extensions.kt │ │ ├── network │ │ ├── ApiService.kt │ │ └── HttpManager.kt │ │ ├── Router.kt │ │ └── ApiServlet.kt │ └── webapp │ └── index.jsp ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── .gitignore ├── VERSION.md ├── gradlew.bat ├── README.md └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'server' 2 | 3 | -------------------------------------------------------------------------------- /pic/gitlab_closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucid-lynxz/WebhookServer/HEAD/pic/gitlab_closed.png -------------------------------------------------------------------------------- /pic/gitlab_merged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucid-lynxz/WebhookServer/HEAD/pic/gitlab_merged.png -------------------------------------------------------------------------------- /pic/gitlab_reopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucid-lynxz/WebhookServer/HEAD/pic/gitlab_reopen.png -------------------------------------------------------------------------------- /pic/gitlab_open_msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucid-lynxz/WebhookServer/HEAD/pic/gitlab_open_msg.png -------------------------------------------------------------------------------- /pic/dignding_contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucid-lynxz/WebhookServer/HEAD/pic/dignding_contact.png -------------------------------------------------------------------------------- /pic/jira_issue_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucid-lynxz/WebhookServer/HEAD/pic/jira_issue_create.png -------------------------------------------------------------------------------- /pic/jira_issue_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucid-lynxz/WebhookServer/HEAD/pic/jira_issue_update.png -------------------------------------------------------------------------------- /pic/pgyer_new_version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucid-lynxz/WebhookServer/HEAD/pic/pgyer_new_version.png -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/config/MessageType.java: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.config; 2 | 3 | /** 4 | * Created by lynxz on 22/03/2017. 5 | * 钉钉消息类型 6 | */ 7 | public interface MessageType { 8 | String LINK = "link"; 9 | String TEXT = "text"; 10 | } 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 25 19:33:07 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/service/ActionService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.service 2 | 3 | import javax.servlet.http.HttpServletRequest 4 | 5 | /** 6 | * Created by lynxz on 30/08/2017. 7 | * 处理一些请求操作,如主动刷新token,更新通讯录等 8 | */ 9 | class ActionService : PlatformService { 10 | override fun process(req: HttpServletRequest?) { 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/service/PlatformService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.service 2 | 3 | import javax.servlet.http.HttpServletRequest 4 | 5 | /** 6 | * Created by lynxz on 28/08/2017. 7 | * 各平台 webhook 处理方法接口 8 | */ 9 | interface PlatformService { 10 | 11 | /** 12 | * 对请求进行判断处理,并按需发送钉钉消息 13 | * */ 14 | fun process(req: HttpServletRequest?) 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/util/DateUtil.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.util 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.* 5 | 6 | object DateUtil { 7 | val formatter = SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS") 8 | 9 | 10 | /** 11 | * 获取当前日期时间字符串,形如: 2019.08.25 21:07:49.829 12 | * */ 13 | fun getCurDateTimeStr() = formatter.format(Date()) 14 | } -------------------------------------------------------------------------------- /src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <%-- 2 | Created by IntelliJ IDEA. 3 | User: lynxz 4 | Date: 22/08/2017 5 | Time: 14:25 6 | To change this template use File | Settings | File Templates. 7 | --%> 8 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 9 | 10 | 11 | $Title$你好啊 12 | 13 | 14 | $END$ 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/config/Actions.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.config 2 | 3 | /** 4 | * Created by lynxz on 21/03/2017. 5 | * merge等hook通知的action字段值 6 | */ 7 | object Actions { 8 | val OPEN = "open"//提交合并请求 9 | val MERGE = "merge"//审核人同意合并 10 | val CLOSE = "close"// 合并申请被关闭 11 | val REOPEN = "reopen"//重开已关闭的merge request 12 | val UPDATE = "upate" // 用户先pull再push然后提交merge请求,此时action值是update 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/AccessTokenBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean 2 | 3 | /** 4 | * Created by lynxz on 07/03/2017. 5 | * 获取钉钉的accessToken时的reponse 6 | * 7 | * errcode : 0 8 | * errmsg : ok 9 | * accessToken : fw8ef8we8f76e6f7s8df8s 10 | */ 11 | data class AccessTokenBean(var errorcode: Int = 0, 12 | var errmsg: String? = null, 13 | var access_token: String? = null) -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/util/CommonUtil.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.util 2 | 3 | import org.apache.logging.log4j.LogManager 4 | import org.apache.logging.log4j.Logger 5 | 6 | object CommonUtil { 7 | 8 | private val fileLogger: Logger = LogManager.getLogger("PersistenceLog") 9 | 10 | /** 11 | * 写入文件信息到文件中 12 | * */ 13 | fun log2File(msg: String? = null) { 14 | if (!msg.isNullOrBlank()) { 15 | fileLogger.error(msg) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/PersistenceDataBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean 2 | 3 | import org.lynxz.server.util.DateUtil 4 | 5 | /** 6 | * request中请求信息持久化(保存到文件或者数据库) 7 | */ 8 | data class PersistenceDataBean(var title: String? = null) { 9 | var message: String? = null // 信息详情 10 | var created: String? = DateUtil.getCurDateTimeStr() // 创建时间 11 | var updated: String? = DateUtil.getCurDateTimeStr() // 更新时间 12 | var timestamp: Long = System.currentTimeMillis() // 创建的时间戳 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/config/PathInfo.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.config 2 | 3 | /** 4 | * Created by lynxz on 30/08/2017. 5 | */ 6 | object PathInfo { 7 | // 刷新token 8 | const val KEY_ACTION_REFRESH_TOKEN = "/action/refreshToken" 9 | // 重新获取通讯录 10 | const val KEY_ACTION_UPDATE_DEPARTMENT_INFO = "/action/updateDepartmentInfo" 11 | // 保存数据到文件或者数据库中 12 | const val KEY_ACTION_SAVE_DATA = "/action/save_data" 13 | // 发送钉钉消息给指定用户或部门 14 | const val KEY_ACTION_SEND_MSG = "/action/send_msg" 15 | 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | build/ 9 | .gradle/ 10 | # BlueJ files 11 | *.ctxt 12 | .idea 13 | config.properties 14 | 15 | # Mobile Tools for Java (J2ME) 16 | .mtj.tmp/ 17 | 18 | # Package Files # 19 | *.jar 20 | *.war 21 | *.ear 22 | *.zip 23 | *.tar.gz 24 | *.rar 25 | bin/ 26 | .settings/ 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | .classpath 31 | .project 32 | src/main/java/ 33 | src/main/resources/ 34 | src/main/webapp/WEB-INF/ 35 | src/test/ 36 | 37 | out/ 38 | local.properties -------------------------------------------------------------------------------- /VERSION.md: -------------------------------------------------------------------------------- 1 | ## V0.1.7 @2019.10.5 2 | 1. 支持通过post请求 `{server}/action/send_msg` 发送消息给tg bot,body内容如下 3 | ```json 4 | { 5 | "name":"目标用户的userName", 6 | "content":"消息内容", 7 | "imType": "Telegram", 8 | "tgBotToken": "接收消息的bot token" 9 | } 10 | ``` 11 | 12 | ## V0.1.6 @2019.9.20 13 | 1. 升级kotlin到1.3.50 14 | 2. 获取部分通讯录详情信息 15 | 3. 可通过手机号或者用户备注信息来定位目标用户 16 | 4. 可通过post请求 `{server}/action/send_msg` 来发送钉钉消息给指定用户或群组,body内容如下 17 | ```json 18 | { 19 | "name":"", 20 | "mobile":"", 21 | "content":"", 22 | "departmentName":"" 23 | } 24 | ``` 25 | 26 | 其中: 27 | * `name` 可空,表示用户名或备注信息 28 | * `mobile` 可空,表示目标用户手机号 29 | * `content` 要发送的消息 30 | * `departmentName` 部门名称, 若用户信息为空,则群发给部门所有人 31 | 32 | ## V0.1.5 33 | 1. 添加 `log4j2.xml`,保存日志在 `logs/log4j2/` 下 34 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/SendMessageReqBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean 2 | 3 | import org.lynxz.server.config.ConstantsPara 4 | 5 | /** 6 | * 用于发送钉钉消息给指定人员或者群组的请求 7 | * */ 8 | data class SendMessageReqBean( 9 | var name: String? = null, // 发送给指定人员的姓名,tg消息则对应其userName 10 | var mobile: String? = null,// 要发送给特定人员的手机号码,匹配优先级高于姓名 11 | var content: String = "", // 消息内容 12 | var departmentName: String? = "", // 部门名称, 若 name 和 mobile 均为空,则发送给group所有人 13 | var imType: String = ImType.DingDing, // 发送消息的im,默认发送到钉钉 14 | var tgBotToken: String = ConstantsPara.defaultTgBotToken // tg接收消息的bot token 15 | ) 16 | 17 | // 支持消息转发的im类型信息 18 | object ImType { 19 | const val DingDing = "DingDing" 20 | const val TG = "Telegram" 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/TgSendMessageReqBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean 2 | 3 | /** 4 | * telegram sendMessage body 5 | * [文档](https://core.telegram.org/bots/api#sendmessage) 6 | * 7 | * 测试请求: curl -X POST "https://api.telegram.org/bot$bot_token/sendMessage" -d "chat_id=$chat_id&text=$msg" 8 | * */ 9 | data class TgSendMessageReqBean( 10 | var chat_id: Long?, // 必填 11 | var text: String?,// 必填 12 | var parse_mode: String = "HTML", // Markdown HTML 13 | var disable_web_page_preview: Boolean = false, 14 | var disable_notification: Boolean = false 15 | ) { 16 | fun isValid() = chat_id != null && !text.isNullOrBlank() 17 | 18 | override fun toString(): String { 19 | return "TgSendMessageReqBean: $chat_id $text" 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/PgyerUpdateHookBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.webhook.bean 2 | 3 | /** 4 | * Created by lynxz on 27/04/2017. 5 | * 蒲公英上传新apk后回调通知 6 | */ 7 | data class PgyerUpdateHookBean(var action: String? = null) { 8 | /** 9 | * action : 应用更新 10 | * title : 声动 11 | * link : https://www.pgyer.com/Cb6T 12 | * message : 您的应用声动有了新的版本(1.0.4)更新。 13 | * type : updateVersion 14 | * os_version : 1.0.4 15 | * build_version : 17 16 | * created : 2017-04-27 10:47:17 17 | * updated : 2017-04-27 10:47:17 18 | * timestamp : 1493261238 19 | * appsize : 15896263 20 | * device_type : Android 21 | * appQRCodeURL : https://static.pgyer.com/app/qrcode/Cb6T 22 | * notes : 23 | */ 24 | 25 | var title: String? = null 26 | var link: String? = null 27 | var message: String? = null 28 | var type: String? = null 29 | var os_version: String? = null 30 | var build_version: String? = null 31 | var created: String? = null 32 | var updated: String? = null 33 | var timestamp: Long = 0 34 | var appsize: String? = null 35 | var device_type: String? = null 36 | var appQRCodeURL: String? = null 37 | var notes: String? = null 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/service/PersistenceService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.service 2 | 3 | import org.apache.logging.log4j.LogManager 4 | import org.lynxz.server.bean.PersistenceDataBean 5 | import org.lynxz.server.config.ConstantsPara 6 | import org.lynxz.server.convertBody 7 | import org.lynxz.server.msec2date 8 | import org.lynxz.server.network.HttpManager 9 | import javax.servlet.http.HttpServletRequest 10 | 11 | /** 12 | * 进行持久化操作 13 | * 对req中的数据进行保存 14 | * */ 15 | class PersistenceService : PlatformService { 16 | private val fileLogger = LogManager.getLogger("PersistenceLog") 17 | 18 | override fun process(req: HttpServletRequest?) { 19 | req?.let { 20 | convertBody(req.inputStream, PersistenceDataBean::class.java)?.let { 21 | fileLogger.info("测试文件======") 22 | 23 | StringBuilder(100).apply { 24 | append("持久化通知 ${it.title}\n") 25 | append("名称: ${it.title}\n") 26 | append("详情: ${it.message}\n") 27 | append("服务器时间: ${msec2date()}") 28 | HttpManager.sendTextMessage(ConstantsPara.defaultNoticeUserName, null, toString()) 29 | } 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/service/PgyerService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.service 2 | 3 | import org.lynxz.server.config.ConstantsPara 4 | import org.lynxz.server.convertBody 5 | import org.lynxz.server.msec2date 6 | import org.lynxz.server.network.HttpManager 7 | import org.lynxz.webhook.bean.PgyerUpdateHookBean 8 | import javax.servlet.http.HttpServletRequest 9 | 10 | /** 11 | * Created by lynxz on 28/08/2017. 12 | * 处理 蒲公英 的 hook 请求 13 | * 需要在蒲公英-我的应用-应用设置-webhook 设置中指定通知的服务器地址 14 | */ 15 | class PgyerService : PlatformService { 16 | override fun process(req: HttpServletRequest?) { 17 | req?.let { 18 | convertBody(req.inputStream, PgyerUpdateHookBean::class.java)?.let { 19 | StringBuilder(100).apply { 20 | append("蒲公英: ${it.action}\n") 21 | append("名称: ${it.title}-${it.device_type}-v${it.os_version}\n") 22 | append("版本说明: ${it.notes ?: "无"}\n") 23 | append("下载链接: ${it.link}\n") 24 | append("二维码链接: ${it.appQRCodeURL}\n") 25 | append("服务器时间: ${msec2date()}") 26 | HttpManager.sendTextMessage(ConstantsPara.defaultNoticeUserName, null, toString()) 27 | } 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/config/KeyNames.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.config 2 | 3 | /** 4 | * Created by lynxz on 25/08/2017. 5 | * 使用到的一些key名称 6 | */ 7 | object KeyNames { 8 | val versionCode = "versionCode" 9 | val versionName = "versionName" 10 | 11 | val corpid = "corpid" 12 | val corpsecret = "corpsecret" 13 | val agentId = "agentId" 14 | val jiraUrl = "jira_borwse_url" 15 | val defaultNoticeUserName = "defaultNoticeUserName" 16 | 17 | // 与钉钉服务器通讯时,需要在url中添加query参数: access_token 18 | val QUERY_KEY_ACCESS_TOKEN = "access_token" 19 | val HEADER_KEY_CONTENT_TYPE = "Content-Type" 20 | 21 | val defaultAssignee = "defaultAssignee" 22 | val gitlabPushMergeBranch = "gitlab_push_merge_branch" 23 | 24 | // tg相关 25 | val defaultTgBotToken = "tg_bot_token" 26 | val defaultTgUserName = "tg_user_name" 27 | 28 | 29 | // httpServlet获取header信息时需要 30 | val HEADER_USER_AGENT = "user-agent" 31 | // header中user-agent字段可能包含如下属性 32 | val HEADER_GIRA = "JIRA" // jira 33 | val HEADER_GITLAB = "X-Gitlab-Event" // gitlab 34 | val HEADER_PGYER = "PgyerWebhookClient" // 蒲公英 35 | val HEADER_JENKINS_PGYER = "jenkins_upload_pgyer" // 自定义的jenkins打包完后上传到蒲公英后发送的通知 36 | 37 | // gitlab请求类型 38 | val HEADER_GITLAB_MERGE_HOOK = "merge request hook" 39 | val HEADER_GITLAB_PUSH_HOOK = "push hook" 40 | 41 | 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/DepartmentMemberDetailListBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean 2 | 3 | /** 4 | * 获取部门用户详情 5 | * 钉钉文档: https://ding-doc.dingtalk.com/doc#/serverapi2/ege851 6 | * */ 7 | data class DepartmentMemberDetailListBean( 8 | val errcode: Int, 9 | val errmsg: String, 10 | val hasMore: Boolean, // 在分页查询时返回,代表是否还有下一页更多数据 11 | val userlist: List, 12 | var departmentId: Int = 0//所属部门id 13 | ) 14 | 15 | // 部门用户详细信息 16 | data class UserDetailInfo( 17 | val active: Boolean, // 表示该用户是否激活了钉钉 18 | val avatar: String, 19 | val department: List, // 成员所属部门id列表 20 | val dingId: String, 21 | val email: String, 22 | val isAdmin: Boolean, 23 | val isBoss: Boolean, 24 | val isHide: Boolean, 25 | val isLeader: Boolean, 26 | val jobnumber: String,// 员工工号 27 | val mobile: String, 28 | val name: String, 29 | val openId: String, 30 | val order: Long, 31 | val position: String, 32 | val stateCode: String, // 国家地区码 33 | val tel: String, // 分机号 34 | val unionid: String, // 员工在当前开发者企业账号范围内的唯一标识,系统生成,固定值,不会改变 35 | val userid: String, // 员工在当前企业内的唯一标识,也称staffId。可由企业在创建时指定,并代表一定含义比如工号,创建后不可修改 36 | val workPlace: String 37 | ) { 38 | var remark: String? = null // 备注, 若为空,则使用name替代 39 | get() = if (field.isNullOrBlank()) name else field 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/Extensions.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server 2 | 3 | import com.google.gson.Gson 4 | import java.io.BufferedReader 5 | import java.io.InputStreamReader 6 | import java.text.SimpleDateFormat 7 | import java.util.* 8 | import javax.servlet.ServletInputStream 9 | 10 | /** 11 | * 将当前时间戳转换为指定格式的日期字符串 12 | * */ 13 | fun msec2date(format: String = "yyyy-MM-dd HH:mm:ss") = SimpleDateFormat(format).format(Date(System.currentTimeMillis())) 14 | 15 | 16 | /** 17 | * 将请求的body部分转换成指定的实体类 18 | */ 19 | internal fun convertBody(sis: ServletInputStream?, clz: Class): T? { 20 | if (sis == null) { 21 | println("convertBody sis is null ,clz is ${clz.simpleName}") 22 | return null 23 | } 24 | 25 | var br: BufferedReader? = null 26 | try { 27 | br = BufferedReader(InputStreamReader(sis, "UTF-8")) 28 | val sb = StringBuilder() 29 | var line = br.readLine() 30 | while (line != null) { 31 | sb.append(line) 32 | line = br.readLine() 33 | } 34 | val bodyStr = sb.toString() 35 | println("${msec2date()} request body is: $bodyStr") 36 | return Gson().fromJson(bodyStr, clz) 37 | } catch (e: Exception) { 38 | e.printStackTrace() 39 | println("${msec2date()} error occurs when convertBody :\n${e.message} ") 40 | } finally { 41 | try { 42 | br?.close() 43 | } catch (e: Exception) { 44 | e.printStackTrace() 45 | } 46 | } 47 | return null 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/config/ConstantsPara.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.config 2 | 3 | import org.lynxz.server.bean.DepartmentListBean 4 | import org.lynxz.server.bean.UserDetailInfo 5 | 6 | /** 7 | * Created by lynxz on 25/08/2017. 8 | * 参数 9 | */ 10 | object ConstantsPara { 11 | // 钉钉服务器地址 12 | val DINGDING_SERVER_URL = "https://oapi.dingtalk.com/" 13 | 14 | // 钉钉相关参数 15 | var dd_corp_id = "" 16 | var dd_corp_secret = "" 17 | var dd_agent_id = "" 18 | var accessToken = "" 19 | var jiraUrl = "" 20 | 21 | /** 22 | * 用于记录部门id和部门名称之间的对应关系 23 | * */ 24 | var departmentNameMap = hashMapOf() 25 | 26 | /** 27 | * 钉钉部门群组 28 | * */ 29 | var departmentList: DepartmentListBean? = null 30 | 31 | /** 32 | *用于存储部门id以及部门内的成员详细信息 33 | * */ 34 | val departmentMemberDetailMap = hashMapOf>() 35 | 36 | /** 37 | * 若钉钉消息的收信人为空,则默认发给此人 38 | * */ 39 | var defaultNoticeUserName = "lynxz" 40 | 41 | /** 42 | * 处理gitlab push or merge hook时, 目标分支匹配时才认为是合并请求 43 | * */ 44 | var targetMergeBranch = "master" 45 | 46 | /** 47 | * 默认发送的目标tg bot token信息 48 | * */ 49 | var defaultTgBotToken = "" 50 | 51 | /** 52 | * 发送消息给bot时,默认的接收用户 53 | * */ 54 | var defaultTgUserName = "" 55 | 56 | /** 57 | * 通过tg getUpdates接口获取其所有的chat id信息 58 | * key: bot_token + "_" + userName/first_name 以便通过接口快速将消息发送给指定人员的bot聊天窗口 59 | * value: chat_id 60 | * */ 61 | var tgChatInfoMap = mutableMapOf() 62 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/MessageTextBean.java: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean; 2 | 3 | /** 4 | * Created by lynxz on 22/03/2017. 5 | * 钉钉发送text类型消息实体 6 | */ 7 | public class MessageTextBean { 8 | 9 | /** 10 | * touser : manager104 11 | * agentid : 8028911 12 | * msgtype : text 13 | * text : {"content":"测试[链接](http://www.baidu.com)"} 14 | */ 15 | private String touser; 16 | private String agentid; 17 | private String msgtype; 18 | private TextBean text; 19 | 20 | public String getTouser() { 21 | return touser; 22 | } 23 | 24 | public void setTouser(String touser) { 25 | this.touser = touser; 26 | } 27 | 28 | public String getAgentid() { 29 | return agentid; 30 | } 31 | 32 | public void setAgentid(String agentid) { 33 | this.agentid = agentid; 34 | } 35 | 36 | public String getMsgtype() { 37 | return msgtype; 38 | } 39 | 40 | public void setMsgtype(String msgtype) { 41 | this.msgtype = msgtype; 42 | } 43 | 44 | public TextBean getText() { 45 | return text; 46 | } 47 | 48 | public void setText(TextBean text) { 49 | this.text = text; 50 | } 51 | 52 | public static class TextBean { 53 | /** 54 | * content : 测试[链接](http://www.baidu.com) 55 | */ 56 | 57 | private String content; 58 | 59 | public String getContent() { 60 | return content; 61 | } 62 | 63 | public void setContent(String content) { 64 | this.content = content; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/TgSendMessageRespBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean 2 | 3 | /** 调用tg sendmessage接口的返回结果 4 | *
 5 |  * {
 6 |  *     "ok": true, // 发送消息收成功
 7 |  *     "error_code": 200,
 8 |  *     "description": "",
 9 |  *     "result": { // ok=true时有值
10 |  *         "message_id": 25,
11 |  *         "from": {
12 |  *             "id": 974962570,
13 |  *             "is_bot": true,
14 |  *             "first_name": "\u77ed\u606f\u8f6c\u53d1\u63a5\u6536bot",
15 |  *             "username": "lynxzz_message_bot"
16 |  *         },
17 |  *         "chat": { // 接收方信息
18 |  *             "id": 417044892,
19 |  *             "first_name": "\u4f60\u8bf4\u5f97\u5bf9tg",
20 |  *             "username": "us3jxjk8d",
21 |  *             "type": "private" // 个人聊天,对应的还有 channel, group,supergroup
22 |  *         },
23 |  *     "date": 1570245182,
24 |  *     "text": "hellobot" // 发送的消息内容
25 |  *     }
26 |  * }
27 |  * 
28 | */ 29 | data class TgSendMessageRespBean( 30 | val ok: Boolean, // 发送消息是否成功 31 | val result: TgRespResult?, // ok=true时有值 32 | val description: String, // ok=false时有值 33 | val error_code: Long// ok=false时有值 34 | ) 35 | 36 | data class TgRespResult( 37 | val chat: TgRespChat, 38 | val date: Long, 39 | val from: TgRespFrom, 40 | val message_id: Long, 41 | val text: String? 42 | ) 43 | 44 | data class TgRespChat( 45 | val first_name: String, 46 | val id: Long, 47 | val type: String, 48 | val username: String 49 | ) 50 | 51 | data class TgRespFrom( 52 | val first_name: String, 53 | val id: Long, 54 | val is_bot: Boolean, 55 | val username: String 56 | ) -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/service/JenkinsService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.service 2 | 3 | import com.google.gson.Gson 4 | import org.lynxz.server.config.ConstantsPara 5 | import org.lynxz.server.msec2date 6 | import org.lynxz.server.network.HttpManager 7 | import org.lynxz.webhook.bean.JenkensUploadPygerBean 8 | import javax.servlet.http.HttpServletRequest 9 | 10 | /** 11 | * Created by lynxz on 28/08/2017. 12 | * 处理自定义的jenkins通知消息 13 | * 这个需要按需在jenkins脚本中调用网络请求处理,不具有通用性 14 | * 我自己用的, 在header中有 "userName" 和 "msg" 字段用于指示谁发起的打包和打包概要说明 15 | * 具体可参考我的博客: http://www.jianshu.com/p/733cfa75ff8b 16 | */ 17 | class JenkinsService : PlatformService { 18 | override fun process(req: HttpServletRequest?) { 19 | req?.let { 20 | val msg = req.getParameter("msg") 21 | Gson().fromJson(msg, JenkensUploadPygerBean::class.java)?.let { 22 | // 发起 jenkins 打包的用户名,同时消息的接收方 23 | // 注意 userName需要与钉钉后台通讯录中的用户名一致或者能够一一映射才可 24 | val userName = req.getParameter("userName") ?: ConstantsPara.defaultNoticeUserName 25 | 26 | StringBuilder(100).apply { 27 | append("jenkins上传apk到蒲公英-code=${it.code}\n") 28 | append("错误消息: ${it.message ?: "无"}") 29 | it.data?.let { 30 | append("名称: ${it.appName}-v${it.appVersion}(build ${it.appBuildVersion})\n") 31 | append("版本说明: ${it.appUpdateDescription}\n") 32 | append("下载链接: https://www.pgyer.com/${it.appShortcutUrl}\n") 33 | append("二维码链接: ${it.appQRCodeURL}\n") 34 | } 35 | append("服务器时间: ${msec2date()}") 36 | HttpManager.sendTextMessage(userName, null, toString()) 37 | } 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/network/ApiService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.network 2 | 3 | import io.reactivex.Observable 4 | import org.lynxz.server.bean.* 5 | import retrofit2.http.* 6 | 7 | /** 8 | * Created by lynxz on 25/08/2017. 9 | * 钉钉相关接口请求 10 | */ 11 | interface ApiService { 12 | 13 | /** 14 | * [获取钉钉AccessToken](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.dfrJ5p&treeId=172&articleId=104980&docType=1) 15 | * @param id corpid 企业id 16 | * @param secret corpsecret 企业应用的凭证密钥 17 | * */ 18 | @GET("gettoken") 19 | fun getAccessToken(@Query("corpid") id: String, @Query("corpsecret") secret: String): Observable 20 | 21 | /** 22 | * [获取部门列表信息](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.xIVqtB&treeId=172&articleId=104979&docType=1#s0) 23 | * 只会返回 用户名 和 id 24 | */ 25 | @GET("department/list") 26 | fun getDepartmentList(): Observable 27 | 28 | /** 29 | * [获取部门用户详情](https://ding-doc.dingtalk.com/doc#/serverapi2/ege851) 30 | * 会返回成员用户的详细信息,包括手机号码,归属部门等 31 | * */ 32 | @GET("user/listbypage") 33 | fun getDepartmentMemberDetailList(@Query("department_id") id: Int = 1, // 部门id,默认为跟部门 34 | @Query("offset") offset: Int = 0, // 偏移量 35 | @Query("size") size: Int = 40, // 分页大小,默认为20,最大100 36 | @Query("order") order: String = "entry_asc" // 列表排序规则, 默认按进入部门的时间升序 37 | ): Observable 38 | 39 | /** 40 | * [向指定用户发送普通文本消息](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.oavHEu&treeId=172&articleId=104973&docType=1#s2) 41 | */ 42 | @POST("message/send") 43 | fun sendTextMessage(@Body bean: MessageTextBean): Observable 44 | 45 | 46 | /** 47 | * [tg getUpdates](https://core.telegram.org/bots/api#getupdates) 48 | * */ 49 | @GET 50 | fun getTgUpdates(@Url url: String): Observable 51 | 52 | /** 53 | * [向tg发送消息](https://core.telegram.org/bots/api#sendmessage) 54 | * */ 55 | @POST 56 | fun sendTgBotMessage(@Url url: String, @Body bean: TgSendMessageReqBean): Observable 57 | 58 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/MessageResponseBean.java: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean; 2 | 3 | /** 4 | * Created by lynxz on 21/03/2017. 5 | * 钉钉发送消息接口的返回信息 6 | */ 7 | public class MessageResponseBean { 8 | 9 | /** 10 | * errcode : 0 11 | * errmsg : ok 12 | * invaliduser : UserID1|UserID2 13 | * invalidparty : PartyID1 14 | * forbiddenUserId : UserID1|UserID2 15 | * messageId : xxxxxxxxxxxxxxxx 16 | */ 17 | private int errcode; 18 | private String errmsg; 19 | 20 | private String invaliduser; 21 | private String invalidparty; 22 | private String forbiddenUserId; 23 | private String messageId; 24 | 25 | public int getErrcode() { 26 | return errcode; 27 | } 28 | 29 | public void setErrcode(int errcode) { 30 | this.errcode = errcode; 31 | } 32 | 33 | public String getErrmsg() { 34 | return errmsg; 35 | } 36 | 37 | public void setErrmsg(String errmsg) { 38 | this.errmsg = errmsg; 39 | } 40 | 41 | public String getInvaliduser() { 42 | return invaliduser; 43 | } 44 | 45 | public void setInvaliduser(String invaliduser) { 46 | this.invaliduser = invaliduser; 47 | } 48 | 49 | public String getInvalidparty() { 50 | return invalidparty; 51 | } 52 | 53 | public void setInvalidparty(String invalidparty) { 54 | this.invalidparty = invalidparty; 55 | } 56 | 57 | public String getForbiddenUserId() { 58 | return forbiddenUserId; 59 | } 60 | 61 | public void setForbiddenUserId(String forbiddenUserId) { 62 | this.forbiddenUserId = forbiddenUserId; 63 | } 64 | 65 | public String getMessageId() { 66 | return messageId; 67 | } 68 | 69 | public void setMessageId(String messageId) { 70 | this.messageId = messageId; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "MessageResponseBean{" + 76 | "errcode=" + errcode + 77 | ", errmsg='" + errmsg + '\'' + 78 | ", invaliduser='" + invaliduser + '\'' + 79 | ", invalidparty='" + invalidparty + '\'' + 80 | ", forbiddenUserId='" + forbiddenUserId + '\'' + 81 | ", messageId='" + messageId + '\'' + 82 | '}'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/Router.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server 2 | 3 | import org.lynxz.server.config.KeyNames 4 | import org.lynxz.server.config.PathInfo 5 | import org.lynxz.server.network.HttpManager 6 | import org.lynxz.server.service.* 7 | import org.lynxz.server.util.CommonUtil 8 | import javax.servlet.http.HttpServletRequest 9 | 10 | /** 11 | * Created by lynxz on 28/08/2017. 12 | * 根据header头部信息,调用对应的处理类 13 | */ 14 | object Router { 15 | fun route(req: HttpServletRequest?) { 16 | req?.let { 17 | val userAgent = req.getHeader(KeyNames.HEADER_USER_AGENT) 18 | val gitlabHeader = req.getHeader(KeyNames.HEADER_GITLAB) 19 | val pathInfo = req.pathInfo 20 | println("${msec2date()} Router userAgent is: $userAgent ,gitlabHeader is: $gitlabHeader ,req.pathInfo is: $pathInfo") 21 | if (!pathInfo.isNullOrBlank()) { 22 | when { 23 | pathInfo.endsWith(PathInfo.KEY_ACTION_REFRESH_TOKEN, true) -> { 24 | HttpManager.refreshAccessToken() 25 | HttpManager.getTgBotUpdates() 26 | } 27 | pathInfo.endsWith(PathInfo.KEY_ACTION_UPDATE_DEPARTMENT_INFO, true) -> HttpManager.getDepartmentInfo() 28 | pathInfo.endsWith(PathInfo.KEY_ACTION_SAVE_DATA, true) -> CommonUtil.log2File(req.queryString) 29 | pathInfo.endsWith(PathInfo.KEY_ACTION_SEND_MSG, true) -> SendMessageService().process(req) 30 | else -> println("cannot process this type of path, ignore....$pathInfo") 31 | } 32 | } else if (!gitlabHeader.isNullOrBlank()) { 33 | GitlabService().process(req) 34 | } else if (!userAgent.isNullOrBlank()) { 35 | when { 36 | userAgent.contains(KeyNames.HEADER_GIRA) -> GiraService().process(req) 37 | userAgent.contains(KeyNames.HEADER_PGYER) -> PgyerService().process(req) 38 | userAgent.contains(KeyNames.HEADER_JENKINS_PGYER) -> JenkinsService().process(req) 39 | else -> println("connot process this type of user-agent: $userAgent") 40 | } 41 | } else { 42 | println("cannot process this type of request, ignore....${req.requestURL}") 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/service/SendMessageService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.service 2 | 3 | import org.lynxz.server.bean.ImType 4 | import org.lynxz.server.bean.SendMessageReqBean 5 | import org.lynxz.server.bean.TgSendMessageReqBean 6 | import org.lynxz.server.config.ConstantsPara 7 | import org.lynxz.server.convertBody 8 | import org.lynxz.server.network.HttpManager 9 | import javax.servlet.http.HttpServletRequest 10 | 11 | /** 12 | * 发送钉钉消息给指定人员或者群组 13 | * */ 14 | class SendMessageService : PlatformService { 15 | override fun process(req: HttpServletRequest?) { 16 | 17 | convertBody(req?.inputStream, SendMessageReqBean::class.java)?.let { 18 | when (it.imType) { 19 | ImType.TG -> { 20 | if (it.name.isNullOrBlank()) { 21 | it.name = ConstantsPara.defaultTgUserName 22 | } 23 | 24 | if (it.tgBotToken.isBlank()) { 25 | it.tgBotToken = ConstantsPara.defaultTgBotToken 26 | } 27 | 28 | val key = "${it.tgBotToken}_${it.name}" 29 | val doOnComplete = { 30 | val chatId = ConstantsPara.tgChatInfoMap[key] 31 | HttpManager.sendTgMessage(TgSendMessageReqBean(chatId, it.content), it.tgBotToken) 32 | } 33 | if (ConstantsPara.tgChatInfoMap[key] == null) { 34 | HttpManager.getTgBotUpdates(it.tgBotToken, doOnComplete) 35 | } else { 36 | doOnComplete.invoke() 37 | } 38 | } 39 | ImType.DingDing -> { 40 | var departmentId = 1 41 | if (it.departmentName.isNullOrBlank() || ConstantsPara.departmentList == null) { 42 | departmentId = 1 43 | } else { 44 | ConstantsPara.departmentList!!.department.forEach { depart -> 45 | if (depart.name.equals(it.departmentName, true)) { 46 | departmentId = depart.id 47 | } 48 | } 49 | } 50 | 51 | HttpManager.sendTextMessage(it.name, it.mobile, it.content, departmentId) 52 | } 53 | } 54 | 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/DepartmentListBean.java: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by lynxz on 08/03/2017. 7 | * 部门列表信息 8 | */ 9 | public class DepartmentListBean { 10 | 11 | /** 12 | * errcode : 0 13 | * errmsg : ok 14 | * department : [{"id":2,"name":"钉钉事业部","parentid":1,"createDeptGroup":true,"autoAddUser":true},{"id":3,"name":"服务端开发组","parentid":2,"createDeptGroup":false,"autoAddUser":false}] 15 | */ 16 | 17 | private int errcode; 18 | private String errmsg; 19 | private List department; 20 | 21 | public int getErrcode() { 22 | return errcode; 23 | } 24 | 25 | public void setErrcode(int errcode) { 26 | this.errcode = errcode; 27 | } 28 | 29 | public String getErrmsg() { 30 | return errmsg; 31 | } 32 | 33 | public void setErrmsg(String errmsg) { 34 | this.errmsg = errmsg; 35 | } 36 | 37 | public List getDepartment() { 38 | return department; 39 | } 40 | 41 | public void setDepartment(List department) { 42 | this.department = department; 43 | } 44 | 45 | public static class DepartmentBean { 46 | /** 47 | * id : 2 48 | * name : 钉钉事业部 49 | * parentid : 1 50 | * createDeptGroup : true 51 | * autoAddUser : true 52 | */ 53 | 54 | private int id; 55 | private String name; 56 | private int parentid; 57 | private boolean createDeptGroup; 58 | private boolean autoAddUser; 59 | 60 | public int getId() { 61 | return id; 62 | } 63 | 64 | public void setId(int id) { 65 | this.id = id; 66 | } 67 | 68 | public String getName() { 69 | return name; 70 | } 71 | 72 | public void setName(String name) { 73 | this.name = name; 74 | } 75 | 76 | public int getParentid() { 77 | return parentid; 78 | } 79 | 80 | public void setParentid(int parentid) { 81 | this.parentid = parentid; 82 | } 83 | 84 | public boolean isCreateDeptGroup() { 85 | return createDeptGroup; 86 | } 87 | 88 | public void setCreateDeptGroup(boolean createDeptGroup) { 89 | this.createDeptGroup = createDeptGroup; 90 | } 91 | 92 | public boolean isAutoAddUser() { 93 | return autoAddUser; 94 | } 95 | 96 | public void setAutoAddUser(boolean autoAddUser) { 97 | this.autoAddUser = autoAddUser; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/JenkensUploadPygerBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.webhook.bean 2 | 3 | /** 4 | * Created by lynxz on 06/05/2017. 5 | * 使用jenkins打包生成pak后,上传到蒲公英,蒲公英返回的响应内容 6 | * 本服务器依据该内容通知钉钉 7 | * [蒲公英上传接口说明](https://www.pgyer.com/doc/api#uploadApp) 8 | */ 9 | data class JenkensUploadPygerBean(var code: Int = 0, 10 | var message: String? = null, 11 | var data: DataBean? = null) { 12 | 13 | /** 14 | * code : 0 15 | * message : 16 | * data : {"appKey":"451359ad7b69bfb21f4d124588f749f0","userKey":"9f2634129de0e58c06366b6e4c355b6f","appType":"2","appIsLastest":"1","appFileSize":"3333852","appName":"PrivatePhoto","appVersion":"0.1.1","appVersionNo":"2","appBuildVersion":"5","appIdentifier":"org.lynxz.privatephoto","appIcon":"33b678170f8b387a6923d428f772a178","appDescription":"","appUpdateDescription":"测试python上传文件功能21","appScreenshots":"","appShortcutUrl":"5faf","appCreated":"2017-05-06 21:00:50","appUpdated":"2017-05-06 21:00:50","appQRCodeURL":"https://static.pgyer.com/app/qrcodeHistory/3a41378d4e000e4562095290920a1142d86a8accddfecab490691faf5486c89a"} 17 | */ 18 | class DataBean { 19 | /** 20 | * appKey : 451359ad7b69bfb21f4d124588f749f0 21 | * userKey : 9f2634129de0e58c06366b6e4c355b6f 22 | * appType : 2 23 | * appIsLastest : 1 24 | * appFileSize : 3333852 25 | * appName : PrivatePhoto 26 | * appVersion : 0.1.1 27 | * appVersionNo : 2 28 | * appBuildVersion : 5 29 | * appIdentifier : org.lynxz.privatephoto 30 | * appIcon : 33b678170f8b387a6923d428f772a178 31 | * appDescription : 32 | * appUpdateDescription : 测试python上传文件功能21 33 | * appScreenshots : 34 | * appShortcutUrl : 5faf 35 | * appCreated : 2017-05-06 21:00:50 36 | * appUpdated : 2017-05-06 21:00:50 37 | * appQRCodeURL : https://static.pgyer.com/app/qrcodeHistory/3a41378d4e000e4562095290920a1142d86a8accddfecab490691faf5486c89a 38 | */ 39 | 40 | var appKey: String? = null 41 | var userKey: String? = null 42 | var appType: String? = null 43 | var appIsLastest: String? = null 44 | var appFileSize: String? = null 45 | var appName: String? = null 46 | var appVersion: String? = null 47 | var appVersionNo: String? = null 48 | var appBuildVersion: String? = null 49 | var appIdentifier: String? = null 50 | var appIcon: String? = null 51 | var appDescription: String? = null 52 | var appUpdateDescription: String? = null 53 | var appScreenshots: String? = null 54 | var appShortcutUrl: String? = null 55 | var appCreated: String? = null 56 | var appUpdated: String? = null 57 | var appQRCodeURL: String? = null 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 使用Gradle和Kotlin重写一下之前 [钉钉通知服务器](https://github.com/lucid-lynxz/Webhook_server) 顺便再熟悉下服务端基本编写操作 2 | 3 | [博客: 打通gitlab与钉钉之间的联系](https://juejin.im/post/5a433b206fb9a0452725de4f) 4 | 5 | ### 1. 配置 `config.properties` 文件参数 6 | clone项目后,请在项目根目录下创建 `src/main/webapp/config.properties` 并设置如下属性值,以便获取钉钉相关参数 7 | ```properties 8 | # 必填,企业在钉钉中的标识,每个企业拥有一个唯一的CorpID 9 | corpid=dingaa******c2f4657eb6378f 10 | # 必填,企业每个应用的凭证密钥 11 | corpsecret=Bp1_HoQej2s******LE7aRWaJm_lYpSMYvVQi-_Q 12 | # 必填,钉钉微应用id 13 | agentId=123456 14 | 15 | # 可选,gitlab默认通知审核的人员或者gira默认通知的bug归属人的真实名字,用于匹配钉钉通讯录获取userId 16 | # 同时也是蒲公英上传应用后回调通知的默认用户 17 | defaultNoticeUserName="张三" 18 | # 可选,jira 详情面板网址前缀(带斜杠),其后添加jira bugId接口拼接成完整地址,若无请放空 19 | jira_borwse_url=http://jira.soundbus.tech/browse/ 20 | # gitlab合并的目标分支是该分支时, 此hook请求才需要发送钉钉消息,不填的话,默认master 21 | gitlab_push_merge_branch=master 22 | 23 | #---IM-Telegram 配置--- 24 | # 用于接收消息的默认bot token 25 | tg_bot_token=9749123470:abced 26 | # 默认接收消息的tg用户username 27 | tg_user_name=helobotxxx 28 | ``` 29 | 30 | ### 2. 通讯录规则 31 | 在通讯录root部门中添加所有人,以便发送消息到特定用户时可以从root部门中查询得到用户id; 32 | 根据gitlab项目路径配置各项目部门,比如: 33 | * gitlab项目地址为: https://gitlab.lynxz.org/demo-android/detail-android 34 | 则表示项目名称(`name`) 为: `detail-android` ,项目所在空间(`namespace`)为: `demo-android` 35 | * 在钉钉后台通讯录中需要先创建部门: `demo_android` ,然后创建其子部门 `detail_android` 36 | 注意: 由于钉钉部门名称不允许使用 `-`,因此创建时改为 `_` 替代 37 | * 目前只支持两级部门结构,若有多个部门符合上述规则gitlab merge通过时会通知所有匹配的部门成员; 38 | ![钉钉通讯录](./pic/dignding_contact.png) 39 | 40 | ### 3. 功能url 41 | 1. `{serverHost}/action/refreshToken` 重新刷新access_token,并重新获取通讯录 42 | 2. `{serverHost}/action/updateDepartmentInfo` 请求该url会立即重新获取钉钉通讯录信息,用于用户更新了钉钉通讯录后主动触发服务器刷新数据 43 | 3. `{serverHost}/action/save_data?**=**` 可以将 query 数据保存到文件中 44 | 如: 1.1.1.1:8080/server-V0.1.4/action/updateDepartmentInfo 45 | 4. `{serverHost}/action/send_msg` post 请求,发送消息给钉钉指定的人员或者部门 46 | ```shell 47 | curl -d "{\"name\":\"\",\"mobile\":\"\",\"content\":\"消息内容\",\"departmentName\":\"部门名称\"}" {serverHost}/action/send_msg 48 | ``` 49 | 5. `{serverHost}/action/send_msg` post 请求,发送消息给钉tg指定用户 50 | ```shell 51 | curl -d "{\"name\":\"目标用户userName\",\"imType\":\"Telegram\",\"content\":\"消息内容\",\"tgBotToken\":\"tg bot token\"}" {serverHost}/action/send_msg 52 | ``` 53 | name: 发送钉钉消息时可空,钉钉用户姓名或者备注名, 发送tg消息时必填,为用户昵称或者userName 54 | content: 必填,要发送的消息 55 | mobile: 可空, 钉钉用户手机号,优先匹配 56 | imType: 可空,用于发送的IM类型,目前支持两种: Telegram 和 DingDing, 默认为 DingDing 57 | departmentName: 发送钉钉消息时必填,为用户所在部门名称 58 | tgBotToken: 用于接收消息的tg机器人token,默认值为 `config.properties` 中 `tg_bot_token` 59 | 60 | 服务端会查询 `departmentName` 部门下的 手机号和名称相符的用户, 若查得到,则发消息给他 61 | 否则,发送消息给该部门所有人 62 | 63 | ### 4. telegram bot使用 64 | 1. 在tg中,`@BotFather` - `/newbot` - `输入任意名称说明` - `输入以 _bot 结尾的名称` - `成功,得到token` 65 | 2. 将上述得到的botToken信息填入 `src/main/webapp/config.properties` 中的 `tg_bot_token` 字段 66 | 3. 将你的tg userName填入 `src/main/webapp/config.properties` 中的 `tg_user_name` 字段,用于默认接收消息用户 67 | 4. 任意发送一条消息给上面创建的bot 68 | 69 | ### 4. 效果介绍 70 | #### 1. gitlab相关 71 | ![gitlab有merge代码审核请求](./pic/gitlab_open_msg.png) 72 | ![gitlab有merge请求被关闭时,会通知提交人](./pic/gitlab_closed.png) 73 | ![gitlab有merge请求被reopen时,会通知审核人](./pic/gitlab_reopen.png) 74 | ![gitlab merge 请求被通过时,会通知相关项目部门所有成员更新代码](./pic/gitlab_merged.png) 75 | 76 | #### 2. jira相关 77 | ![jira上有新的 issue 被创建时,会通知issue归属用户](./pic/jira_issue_create.png) 78 | ![jira上有新的 issue 有更新时,会通知issue归属用户](./pic/jira_issue_update.png) 79 | 80 | #### 3. 蒲公英 81 | ![蒲公英上有新版本的app上传成功时,会通知指定用户](./pic/pgyer_new_version.png) -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/service/GiraService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.service 2 | 3 | import org.lynxz.server.bean.JiraBugEventBean 4 | import org.lynxz.server.config.ConstantsPara 5 | import org.lynxz.server.convertBody 6 | import org.lynxz.server.msec2date 7 | import org.lynxz.server.network.HttpManager 8 | import java.io.IOException 9 | import javax.servlet.http.HttpServletRequest 10 | 11 | /** 12 | * Created by lynxz on 28/08/2017. 13 | * 处理gira的webhook通知 14 | */ 15 | class GiraService : PlatformService { 16 | 17 | @Throws(IOException::class) 18 | override fun process(req: HttpServletRequest?) { 19 | req?.let { 20 | convertBody(req.inputStream, JiraBugEventBean::class.java)?.let { 21 | val bean = it 22 | /* 23 | * issue 创建或修改的操作人 24 | * */ 25 | val user = bean.user 26 | 27 | val event = bean.webhookEvent 28 | val issue = bean.issue 29 | val fields = issue.fields 30 | 31 | val affectLabels = fields.labels//测试版本号 32 | val type = fields.issuetype.name//issue类型,如 Bug 33 | val projectName = fields.project.key 34 | 35 | val creatorName = fields.creator.displayName 36 | val summary = fields.summary// bug标题 37 | val keyId = issue.key// bug编号,如 UPLUSGO-1241 38 | val url = ConstantsPara.jiraUrl + keyId//issue详情访问网址 39 | var assigneeName = fields.assignee.displayName//bug归属人 40 | 41 | /* 42 | * issue 状态信息 43 | * */ 44 | val status = fields.status 45 | 46 | /* 47 | * 存在 changelog 对象时,表明 issue 内容被修改过,比如标题描述 48 | * 测试了下,属性 items 是个列表,但改变多次也只有最新的一条记录 49 | * */ 50 | val changelog = bean.changelog 51 | 52 | /* 53 | * issue 的备注信息列表 54 | * 按提交 comment 的先后顺序排列 55 | * 测试过,一定有值 56 | * */ 57 | val comment = fields.comment 58 | val comments = comment.comments 59 | 60 | val sb = StringBuilder(100).apply { 61 | append("$event\n") 62 | append("人员: ${user.displayName}\n") 63 | append("归属: $assigneeName\n") 64 | append("类型: $type (状态: ${status.name} )\n") 65 | append("项目: $projectName - $affectLabels\n") 66 | append("概要: $summary\n") 67 | 68 | if (changelog != null) { 69 | val items = changelog.items 70 | if (items.size >= 1) { 71 | append("编辑issue: ${items[0].field}->${items[0].toString}\n") 72 | } 73 | } else if (comments.size > 0) { 74 | append("最新备注: ${comments[comments.size - 1].body}\n") 75 | } 76 | 77 | append("点击查看: $url\n") 78 | append("服务器时间: ${msec2date()}") 79 | } 80 | 81 | // println("要发送的jira信息为: $sb") 82 | assigneeName = if (assigneeName.isNullOrBlank()) ConstantsPara.defaultNoticeUserName else assigneeName 83 | HttpManager.sendTextMessage(assigneeName, null, sb.toString()) 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/TgGetUpdateResponseBean.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean 2 | 3 | /** 4 | * tg 获取 bot新消息列表的响应 5 | * [文档](https://core.telegram.org/bots/api#getupdates) 6 | * 7 | ```json 8 | * { 9 | * "ok": true, 10 | * "result": [{ 11 | * "update_id": 629414424, 12 | * "channel_post": { // 来自于channel 13 | * "message_id": 22, 14 | * "chat": { 15 | * "id": -1001232599787, // channel 的 chat_id,用于回复 16 | * "title": "11_\u6280\u672f\u5b66\u4e60\ud83e\udd13", // channel名称 17 | * "type": "channel" 18 | * }, 19 | * "date": 1570239840, 20 | * "text": "\u6d4b\u8bd5" // 消息内容 21 | * } 22 | * },{ 23 | * "update_id": 629414427, 24 | * "message": { // 普通消息,直接通过机器人聊天发送的消息 25 | * "message_id": 8, 26 | * "from": { 27 | * "id": 417044892, // 发送人 id 28 | * "is_bot": false, // 发送人是否是bot 29 | * "first_name": "\u4f60\u8bf4\u5f97\u5bf9tg", // 发送人昵称 30 | * "username": "us3jxjk8d", // user name 31 | * "language_code": "en" 32 | * }, 33 | * "chat": { 34 | * "id": 417044892, 35 | * "first_name": "\u4f60\u8bf4\u5f97\u5bf9tg", 36 | * "username": "us3jxjk8d", 37 | * "type": "private" 38 | * }, 39 | * "date": 1570239945, 40 | * "text": "\u5f88\u7cbe\u660e\u7a7a\u7075" // 消息内容 41 | * } 42 | * }, { 43 | * "update_id": 629414429, 44 | * "message": { 45 | * "message_id": 9, 46 | * "from": { 47 | * "id": 417044892, 48 | * "is_bot": false, 49 | * "first_name": "\u4f60\u8bf4\u5f97\u5bf9tg", 50 | * "username": "us3jxjk8d", 51 | * "language_code": "en" 52 | * }, 53 | * "chat": { 54 | * "id": -394752664, 55 | * "title": "testbotgroup\ud83d\ude44," 56 | * "type": "group", // 群组消息 57 | * "all_members_are_administrators": true 58 | * }, 59 | * "date": 1570240581, 60 | * "group_chat_created": true 61 | * } 62 | * }] 63 | * } 64 | * 65 | * */ 66 | data class TgGetUpdateResponseBean( 67 | val ok: Boolean, 68 | val result: List? 69 | ) 70 | 71 | data class Result( 72 | val channel_post: ChannelPost?, 73 | val message: Message?, 74 | val update_id: Long 75 | ) 76 | 77 | data class ChannelPost( 78 | val chat: ChannelChat, 79 | val date: Long, 80 | val message_id: Long, 81 | val text: String 82 | ) 83 | 84 | data class ChannelChat( 85 | val id: Long, 86 | val title: String, 87 | val type: String 88 | ) 89 | 90 | data class Message( 91 | val chat: MessageChat, 92 | val date: Long, 93 | val from: From, 94 | val message_id: Long, 95 | val text: String 96 | ) 97 | 98 | data class MessageChat( 99 | val first_name: String?, // 昵称 100 | val id: Long, 101 | val type: String, 102 | val username: String? // userName 103 | ) 104 | 105 | data class From( 106 | val first_name: String, 107 | val id: Long, 108 | val is_bot: Boolean, 109 | val language_code: String, 110 | val username: String 111 | ) -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/ApiServlet.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server 2 | 3 | import io.reactivex.Observable 4 | import org.lynxz.server.config.ConstantsPara 5 | import org.lynxz.server.config.KeyNames 6 | import org.lynxz.server.network.HttpManager 7 | import java.io.File 8 | import java.io.FileInputStream 9 | import java.io.InputStreamReader 10 | import java.util.* 11 | import java.util.concurrent.TimeUnit 12 | import javax.servlet.ServletConfig 13 | import javax.servlet.annotation.WebServlet 14 | import javax.servlet.http.HttpServlet 15 | import javax.servlet.http.HttpServletRequest 16 | import javax.servlet.http.HttpServletResponse 17 | import kotlin.collections.HashMap 18 | 19 | 20 | /** 21 | * Created by lynxz on 25/08/2017. 22 | * 对所有请求进行处理 23 | * 关于 [@WebServlet的使用](http://blog.csdn.net/maozi_bsz/article/details/46431189) 24 | */ 25 | @WebServlet(name = "home", value = ["/*"], loadOnStartup = 1) 26 | class ApiServlet : HttpServlet() { 27 | 28 | override fun init(config: ServletConfig?) { 29 | super.init(config) 30 | getConfigPath("config.properties").let { 31 | // 避免多次初始化 32 | if (ConstantsPara.dd_corp_id.isBlank()) { 33 | println("init configPath is $it \n${File(it).exists()}") 34 | loadConfig(it) 35 | // access_token有效期7200秒 36 | Observable.interval(0, 3600, TimeUnit.SECONDS) 37 | .subscribe { 38 | HttpManager.refreshAccessToken() 39 | HttpManager.getTgBotUpdates() 40 | } 41 | } 42 | } 43 | } 44 | 45 | override fun doGet(req: HttpServletRequest?, resp: HttpServletResponse?) { 46 | doPost(req, resp) 47 | } 48 | 49 | override fun doPost(req: HttpServletRequest?, resp: HttpServletResponse?) { 50 | processRequest(req, resp) 51 | } 52 | 53 | private fun processRequest(req: HttpServletRequest?, resp: HttpServletResponse?) { 54 | req?.characterEncoding = "UTF-8" 55 | getHeadersInfo(req) 56 | Router.route(req) 57 | resp?.apply { 58 | characterEncoding = "UTF-8" 59 | // 返回给客户端的数据 60 | writer.apply { 61 | // todo 2019.10.5 返回实际调用结果 62 | write("{\"serverTime\":\"${msec2date()} hello, I'm running...\"}") // 使用println(...) 等效 63 | flush() 64 | close() 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * 加载指定根目录下指定路径的属性文件 71 | * @param configPath 文件路径,如 "/config.properties" 72 | * @return 属性对象 Properties 73 | */ 74 | private fun loadConfig(configPath: String) { 75 | if (File(configPath).exists()) { 76 | Properties().apply { 77 | load(InputStreamReader(FileInputStream(File(configPath)), "UTF-8")) 78 | // load(FileInputStream(File(configPath))) 79 | ConstantsPara.dd_corp_id = getProperty(KeyNames.corpid) 80 | ConstantsPara.dd_corp_secret = getProperty(KeyNames.corpsecret) 81 | ConstantsPara.dd_agent_id = getProperty(KeyNames.agentId) 82 | ConstantsPara.jiraUrl = getProperty(KeyNames.jiraUrl) 83 | ConstantsPara.defaultNoticeUserName = getProperty(KeyNames.defaultNoticeUserName) 84 | ConstantsPara.targetMergeBranch = getProperty(KeyNames.gitlabPushMergeBranch) 85 | ConstantsPara.defaultTgBotToken = getProperty(KeyNames.defaultTgBotToken) 86 | ConstantsPara.defaultTgUserName = getProperty(KeyNames.defaultTgUserName) 87 | println("the corp id is: ${ConstantsPara.dd_corp_id} ,defaultNoticeUserName = ${ConstantsPara.defaultNoticeUserName}") 88 | } 89 | } 90 | } 91 | 92 | //将配置文件放置于 src/main/webapp 目录下 93 | private fun getConfigPath(fileName: String) = "${servletContext.getRealPath("/")}$fileName" 94 | 95 | /** 96 | * 获取header信息 97 | */ 98 | private fun getHeadersInfo(request: HttpServletRequest?) = HashMap().apply { 99 | request?.headerNames?.let { 100 | while (it.hasMoreElements()) { 101 | val key = it.nextElement() 102 | put(key, request.getHeader(key)) 103 | } 104 | } 105 | println("${msec2date()} header of this request: ${toString()}") 106 | } 107 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/service/GitlabService.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.service 2 | 3 | import org.lynxz.server.bean.GbMergeRequestEventBean 4 | import org.lynxz.server.bean.GbPushRequestEventBean 5 | import org.lynxz.server.config.Actions 6 | import org.lynxz.server.config.ConstantsPara 7 | import org.lynxz.server.config.KeyNames 8 | import org.lynxz.server.convertBody 9 | import org.lynxz.server.msec2date 10 | import org.lynxz.server.network.HttpManager 11 | import java.util.regex.Pattern 12 | import javax.servlet.http.HttpServletRequest 13 | 14 | /** 15 | * Created by lynxz on 28/08/2017. 16 | * 处理gitlab的合并请求 17 | * 假设目标分支是master,可能出现两种情况的hook: 18 | * 1. 在master分支上的push hook; 19 | * 2. 正常的merge hook; 20 | */ 21 | class GitlabService : PlatformService { 22 | /* 23 | * 上次merge后的commit_sha值 24 | * 由于merge hook有时候不发出,因此之前多加了一个push hook处理 25 | * 但是有时merge hook 又发出,则会出现两次通知(master分支的push hook测试后每次都有收到) 26 | * 因此在此处添加一个commitId作为判断依据,避免多次重复同样的消息 27 | */ 28 | companion object { 29 | var lastMergeCommitSha: String? = "" 30 | } 31 | 32 | override fun process(req: HttpServletRequest?) { 33 | val gitlabHookType = req?.getHeader(KeyNames.HEADER_GITLAB) 34 | println("gitlabService gitlabHookType: ${gitlabHookType?.toLowerCase()}") 35 | when (gitlabHookType?.toLowerCase()) { 36 | KeyNames.HEADER_GITLAB_PUSH_HOOK -> processPushHook(req) 37 | KeyNames.HEADER_GITLAB_MERGE_HOOK -> processMergeHook(req) 38 | else -> println("GitlabService cannot distinguish $gitlabHookType") 39 | } 40 | } 41 | 42 | /** 43 | * 处理gitlab的merge请求通知 44 | */ 45 | private fun processMergeHook(req: HttpServletRequest) { 46 | convertBody(req.inputStream, GbMergeRequestEventBean::class.java)?.let { 47 | val objectAttributes = it.object_attributes 48 | val action = objectAttributes.action 49 | println("processMergeHook action = " + action + " , url = " + objectAttributes.url) 50 | val user = it.user 51 | val project = it.project 52 | StringBuilder(100).apply { 53 | when (action) { 54 | 55 | Actions.OPEN, Actions.REOPEN, Actions.UPDATE -> {// 有新的merge请求时,通知审核人员审核 56 | append("Gitlab: 有新merge请求: $action\n") 57 | append("项目: ${project.name}\n") 58 | append("用户: ${user.name}\n") 59 | append("目标分支: ${objectAttributes.target_branch}\n") 60 | append("请求概要: ${objectAttributes.title}\n") 61 | append("服务器时间: ${msec2date()}\n") 62 | append("审核地址: ${objectAttributes.url}") 63 | HttpManager.sendTextMessage(it.assignee.name, null, toString()) 64 | } 65 | 66 | Actions.CLOSE -> {// merge请求被关闭的时候,通知提交请求的人 67 | append("Gitlab: 您的merge请求被关闭\n") 68 | .append("项目: ${project.name}\n") 69 | .append("目标分支: ${objectAttributes.target_branch}\n") 70 | .append("概要: ${objectAttributes.title}\n") 71 | .append("服务器时间: ${msec2date()}") 72 | HttpManager.sendTextMessage(user.name, null, toString()) 73 | } 74 | 75 | Actions.MERGE -> {// merge请求被通过,通知相关所有人更新代码 76 | val mergeCommitSha = objectAttributes.merge_commit_sha 77 | println("${msec2date()} processMergeHook $lastMergeCommitSha $mergeCommitSha") 78 | if (lastMergeCommitSha.isNullOrBlank() or 79 | !lastMergeCommitSha.equals(mergeCommitSha, ignoreCase = true)) { 80 | lastMergeCommitSha = mergeCommitSha 81 | 82 | append("Gitlab: 有merge请求被通过,请更新代码").append("\n") 83 | append("项目: ${project.name}\n") 84 | append("分支: ${objectAttributes.target_branch}\n") 85 | append("概要: ${objectAttributes.title}\n") 86 | append("服务器时间: ${msec2date()}") 87 | HttpManager.sendTestMessageToDepartment(toString(), project.name, project.namespace) 88 | } 89 | } 90 | 91 | else -> { 92 | append("Gitlab: 有新merge请求: ").append(action).append("\n") 93 | append("项目: ${project.name}\n") 94 | append("用户: ${user.username}\n") 95 | append("目标分支: ${objectAttributes.target_branch}\n") 96 | append("请求概要: ${objectAttributes.title}\n") 97 | append("服务器时间: ${msec2date()}\n") 98 | append("审核地址: ${objectAttributes.url}") 99 | HttpManager.sendTextMessage(it.assignee.name, null, toString()) 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * 判断gitlab push hook 是否是 merge hook,是的话就通知对应人员 108 | */ 109 | private fun processPushHook(req: HttpServletRequest) { 110 | convertBody(req.inputStream, GbPushRequestEventBean::class.java)?.let { 111 | /* 112 | * 包含 "master" 字样的话就说明是merge hook 113 | * 具体哪个分支根据 'config.properties' 文件中的 'gitlab_push_merge_branch' 属性来确定 114 | * ref : refs/heads/MASTER 115 | * */ 116 | if (!it.ref.toLowerCase().contains(ConstantsPara.targetMergeBranch)) { 117 | return 118 | } 119 | 120 | val after = if (it.after.isNullOrBlank()) it.checkout_sha else it.after 121 | 122 | // 若之前已经处理过该id的消息,则不作处理 123 | println("${msec2date()} processPushHook $lastMergeCommitSha $after") 124 | if (lastMergeCommitSha?.equals(after, ignoreCase = true) ?: false) { 125 | return 126 | } 127 | lastMergeCommitSha = after 128 | // 获取仓库信息,用于判断是通知哪个部门的人,如Android/iOS 129 | val repository = it.repository 130 | val repositoryName = repository.name 131 | 132 | // 获取提交信息 133 | val commits = it.commits 134 | if (commits?.size ?: 0 > 0) { 135 | val commitsBean = commits[commits.size - 1] 136 | var mergeInfo = commitsBean.message.replace("\\r", "").replace("\\n", "") 137 | 138 | // 去除 master 分支 push 信息中的无用字符串 139 | val p = Pattern.compile("Merge branch\\D.+into\\s." + ConstantsPara.targetMergeBranch + ".") 140 | val matcher = p.matcher(mergeInfo) 141 | val s1 = matcher.replaceFirst("") 142 | 143 | val p1 = Pattern.compile("See\\smerge.+$") 144 | val matcher1 = p1.matcher(s1) 145 | mergeInfo = matcher1.replaceAll("") 146 | 147 | StringBuilder(100).apply { 148 | append("Gitlab: ${ConstantsPara.targetMergeBranch}分支有代码更新,请刷新本地代码\n") 149 | append("项目: $repositoryName\n") 150 | append("分支: ${ConstantsPara.targetMergeBranch}\n") 151 | append("概要: $mergeInfo\n") 152 | append("服务器时间: ${msec2date()}") 153 | HttpManager.sendTestMessageToDepartment(toString(), it.project.name, it.project.namespace) 154 | } 155 | } 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/network/HttpManager.kt: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.network 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.Observer 5 | import io.reactivex.disposables.CompositeDisposable 6 | import io.reactivex.disposables.Disposable 7 | import io.reactivex.functions.BiFunction 8 | import io.reactivex.rxkotlin.toObservable 9 | import io.reactivex.schedulers.Schedulers 10 | import okhttp3.Interceptor 11 | import okhttp3.OkHttpClient 12 | import okhttp3.logging.HttpLoggingInterceptor 13 | import org.lynxz.server.bean.* 14 | import org.lynxz.server.config.ConstantsPara 15 | import org.lynxz.server.config.KeyNames 16 | import org.lynxz.server.config.MessageType 17 | import org.lynxz.server.msec2date 18 | import retrofit2.Retrofit 19 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 20 | import retrofit2.converter.gson.GsonConverterFactory 21 | 22 | 23 | /** 24 | * Created by lynxz on 25/08/2017. 25 | * 网络访问具体处理类 26 | */ 27 | object HttpManager { 28 | 29 | // 给请求添加统一的query参数:access_token 30 | private val queryInterceptor = Interceptor { chain -> 31 | val original = chain.request() 32 | val url = original.url().newBuilder() 33 | .addQueryParameter(KeyNames.QUERY_KEY_ACCESS_TOKEN, ConstantsPara.accessToken) 34 | .build() 35 | 36 | val requestBuilder = original.newBuilder().url(url) 37 | chain.proceed(requestBuilder.build()) 38 | } 39 | 40 | // 给请求添加统一的header参数:Content-Type 41 | private val headerInterceptor = Interceptor { chain -> 42 | val request = chain.request().newBuilder() 43 | .addHeader(KeyNames.HEADER_KEY_CONTENT_TYPE, "application/json") 44 | .build() 45 | chain.proceed(request) 46 | } 47 | 48 | // 显示请求日志,可选 49 | private val logInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } 50 | 51 | private val okHttpClient: OkHttpClient = OkHttpClient() 52 | .newBuilder() 53 | .addInterceptor(headerInterceptor) 54 | .addInterceptor(queryInterceptor) 55 | .addInterceptor(logInterceptor) 56 | .build() 57 | 58 | private val ddRetrofit: Retrofit = Retrofit.Builder() 59 | .client(okHttpClient) 60 | .baseUrl(ConstantsPara.DINGDING_SERVER_URL) 61 | .addConverterFactory(GsonConverterFactory.create()) 62 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 63 | .build() 64 | 65 | private val apiService: ApiService = ddRetrofit.create(ApiService::class.java) 66 | 67 | private val compositeDisposable = CompositeDisposable() 68 | 69 | /** 70 | * 刷新钉钉accessToken 71 | * */ 72 | fun refreshAccessToken() { 73 | apiService.getAccessToken(ConstantsPara.dd_corp_id, ConstantsPara.dd_corp_secret) 74 | .retry(1) 75 | .subscribe(object : Observer { 76 | override fun onError(e: Throwable) { 77 | e.printStackTrace() 78 | } 79 | 80 | override fun onSubscribe(d: Disposable) { 81 | addDisposable(d) 82 | } 83 | 84 | override fun onComplete() { 85 | } 86 | 87 | override fun onNext(t: AccessTokenBean) { 88 | println("refreshAccessToken $t") 89 | ConstantsPara.accessToken = t.access_token ?: "" 90 | // todo 若有上一次未发送成功的数据,则尝试发送 91 | getDepartmentInfo() 92 | } 93 | }) 94 | } 95 | 96 | /** 97 | * 获取部门列表信息以及各部门成员信息 98 | */ 99 | fun getDepartmentInfo() { 100 | apiService.getDepartmentList() 101 | .flatMap { list -> 102 | ConstantsPara.departmentList = list 103 | list.department.forEach { ConstantsPara.departmentNameMap[it.id] = it.name } 104 | Observable.fromIterable(list.department) 105 | } 106 | .map { departmentBean -> departmentBean.id } 107 | .flatMap { departmentId -> 108 | Observable.zip(Observable.create { it.onNext(departmentId) }, 109 | apiService.getDepartmentMemberDetailList(departmentId), 110 | BiFunction { t1, t2 -> 111 | t2.departmentId = t1 112 | t2 113 | }) 114 | } 115 | .retry(1) 116 | .subscribe(object : Observer { 117 | override fun onNext(t: DepartmentMemberDetailListBean) { 118 | ConstantsPara.departmentMemberDetailMap[t.departmentId] = t.userlist 119 | } 120 | 121 | override fun onSubscribe(d: Disposable) { 122 | addDisposable(d) 123 | } 124 | 125 | override fun onError(e: Throwable) { 126 | e.printStackTrace() 127 | } 128 | 129 | override fun onComplete() { 130 | println("getDepartmentInfo onComplete:\n${ConstantsPara.departmentMemberDetailMap.keys.forEach { println("departId: $it") }}") 131 | // sendTextMessage(ConstantsPara.defaultNoticeUserName, null, "test from server") 132 | } 133 | }) 134 | } 135 | 136 | /** 137 | * 根据gitlab返回的项目组别和名称,发送消息给对应的部门群成员 138 | * 钉钉部门名称与项目名称一致,切部门的上级部门与项目所在组的namespace一致,则可确定要通知的部门 139 | * 140 | * 如钉钉通讯录中有某群: father/child , 部门名为 child, 上级部门为 father 141 | * 而gitlab中有某项目地址为: https://gitlab.lynxz.org/father/child 142 | * 则可完全确定所需通知的部门child 143 | * 备注: 本项目只支持两级 144 | * */ 145 | fun sendTestMessageToDepartment(msg: String? = "", projectName: String? = "", projectNameSpace: String = "") { 146 | if (msg.isNullOrBlank() or projectName.isNullOrBlank()) { 147 | return 148 | } 149 | 150 | // 由于钉钉部门名称不支持 "-" ,因此自动替换为 "_",创建通讯录时请注意 151 | ConstantsPara.departmentList?.department?.toObservable() 152 | ?.filter { (projectName!!.replace("-", "_").equals(it.name, true)) and (projectNameSpace.replace("-", "_").equals(ConstantsPara.departmentNameMap[it.parentid], true)) } 153 | ?.flatMap { bean -> Observable.just(ConstantsPara.departmentMemberDetailMap[bean.id]) } 154 | ?.flatMap { Observable.fromIterable(it) } 155 | ?.doOnNext { sendTextMessage(it.name, null, msg!!) } 156 | ?.doOnSubscribe { println("要群发给 $projectNameSpace/$projectName 的消息是:\n${toString()}") } 157 | ?.subscribe() 158 | } 159 | 160 | /** 161 | * 向指定用户发送文本内容[message] 162 | * 若目标用户为空,则发送给指定部门[departmentId]所有人,比如gitlab merge请求通过时,通知所有人 163 | * 优先通过手机号匹配用户, [userName] 只要匹配用户名或者用户备注信息即算符合要求 164 | * 165 | * @param userMobile 用户手机号 166 | * @param userName 用户姓名 167 | * 168 | * */ 169 | fun sendTextMessage(userName: String? = null, userMobile: String? = null, 170 | message: String = "", departmentId: Int = 1) { 171 | ConstantsPara.departmentMemberDetailMap[departmentId]?.apply { 172 | stream().filter { userMobile.isNullOrBlank() or userMobile.equals(it.mobile, true) } 173 | .filter { userName.isNullOrBlank() or userName.equals(it.name, true) or userName.equals(it.remark, true) } 174 | .forEach { 175 | val textBean = MessageTextBean().apply { 176 | touser = it.userid 177 | agentid = ConstantsPara.dd_agent_id 178 | msgtype = MessageType.TEXT 179 | text = MessageTextBean.TextBean().apply { 180 | // 自动添加时间信息,避免被钉钉服务器拦截 181 | content = if (message.contains("服务器时间")) message else "$message\n服务器时间: ${msec2date()}" 182 | } 183 | } 184 | apiService.sendTextMessage(textBean) 185 | .subscribeOn(Schedulers.io()) 186 | .subscribe(object : Observer { 187 | override fun onComplete() { 188 | } 189 | 190 | override fun onSubscribe(d: Disposable) { 191 | addDisposable(d) 192 | } 193 | 194 | override fun onNext(t: MessageResponseBean) { 195 | println("${msec2date()} sendTextMessage $t") 196 | } 197 | 198 | override fun onError(e: Throwable) { 199 | e.printStackTrace() 200 | } 201 | }) 202 | } 203 | } 204 | } 205 | 206 | 207 | /** 208 | * 获取tg bot的所有聊天信息,主要用于获取 chat_id 209 | * */ 210 | fun getTgBotUpdates(botToken: String = ConstantsPara.defaultTgBotToken, doOnComplete: () -> Unit = {}) { 211 | apiService.getTgUpdates("https://api.telegram.org/bot$botToken/getUpdates") 212 | .retry(1) 213 | .subscribe(object : Observer { 214 | override fun onError(e: Throwable) { 215 | e.printStackTrace() 216 | } 217 | 218 | override fun onSubscribe(d: Disposable) { 219 | addDisposable(d) 220 | } 221 | 222 | override fun onComplete() { 223 | doOnComplete.invoke() 224 | } 225 | 226 | override fun onNext(t: TgGetUpdateResponseBean) { 227 | if (t.ok) { 228 | val chatMap = mutableMapOf() 229 | 230 | t.result?.filter { 231 | // 只取私聊,非channel/group机器人 232 | it.message?.chat?.type == "private" 233 | }?.forEach { 234 | it.message?.chat?.let { chat -> 235 | // userName可能为空,缓存userName和firstName,减少客户端对接成本 236 | val firstName = chat.first_name 237 | val userName = chat.username 238 | 239 | if (!firstName.isNullOrBlank()) { 240 | chatMap["${botToken}_$firstName"] = chat.id 241 | } 242 | 243 | if (!userName.isNullOrBlank()) { 244 | chatMap["${botToken}_$userName"] = chat.id 245 | } 246 | } 247 | } 248 | ConstantsPara.tgChatInfoMap = chatMap 249 | } 250 | } 251 | }) 252 | } 253 | 254 | /** 255 | * 通过bot,发送tg消息给指定人员 256 | * */ 257 | fun sendTgMessage(body: TgSendMessageReqBean, botToken: String = ConstantsPara.defaultTgBotToken) { 258 | if (!body.isValid()) { 259 | print("发送tg消息失败,内容异常,请检查$body") 260 | return 261 | } 262 | apiService.sendTgBotMessage("https://api.telegram.org/bot$botToken/sendMessage", body) 263 | .retry(1) 264 | .subscribe(object : Observer { 265 | override fun onError(e: Throwable) { 266 | e.printStackTrace() 267 | } 268 | 269 | override fun onSubscribe(d: Disposable) { 270 | addDisposable(d) 271 | } 272 | 273 | override fun onComplete() { 274 | } 275 | 276 | override fun onNext(t: TgSendMessageRespBean) { 277 | println("发送消息给tg bot $botToken, 结果: ${t.ok}\n内容:${t.result?.text}") 278 | } 279 | }) 280 | } 281 | 282 | 283 | fun addDisposable(d: Disposable) { 284 | compositeDisposable.add(d) 285 | } 286 | } -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/GbPushRequestEventBean.java: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by lynxz on 10/04/2017. 7 | * 偶尔审核人merge gitlab请求通过时,gitlab发送的hook是push hook,格式如下 8 | */ 9 | public class GbPushRequestEventBean { 10 | 11 | /** 12 | * object_kind : push 13 | * before : 43b864d91ca0af1fdeeff609a64499b7e1afa72 14 | * after : 3340795ced98e7a6c86f26612c63a55efa868a5 15 | * ref : refs/heads/MASTER 16 | * checkout_sha : 334079ced978e7a6c86f26612c63a55efa868a5 17 | * message : null 18 | * user_id : 31 19 | * user_name : 张三 20 | * user_email : lz@st.c.c 21 | * user_avatar : https://gitlab.sous.tech/upads/user/avar/31/ly_aatar.png 22 | * project_id : 216 23 | * project : {"name":"sonicmng-android","description":" andorid版本","web_url":"https://gitlab.soundbus.th/sonving-app-team/sonicmoving-android","avatar_url":null,"git_ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","git_http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git","namespace":"sonicmoving-app-team","visibility_level":0,"path_with_namespace":"sonicmoving-app-team/sonicmoving-android","default_branch":"MASTER","homepage":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git"} 24 | * commits : [{"id":"50f4f5d56e32c74959a4c03a23e18fedc20cea8f","message":"第三方绑定信息UI细节调整\n","timestamp":"2017-04-10T16:30:26+08:00","url":"https://gitlab.soundbus.th/sonicmoving-app-team/sonicmoving-android/commit/50f4f5d56e32c74959a4c03a28fedc20cea8f","author":{"name":"zhuyic","email":"9663202@qq.com"},"added":[],"modified":["app/src/main/res/layout/activity_account_security.xml","app/src/main/res/values/strings.xml"],"removed":[]},{"id":"c6531663349ba042bd8ce1adda232ae3ed8bf9d0","message":"Merge branch 'MASTER' of https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android into zyc_sonicmoving\n","timestamp":"2017-04-10T16:30:44+08:00","url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android/commit/c6531663349ba042bd8ce1adda232ae3ed8bf9d0","author":{"name":"zhuyichao","email":"97663202@qq.com"},"added":[],"modified":[],"removed":[]},{"id":"3340795ced978e7a6c86f26612c63a55efa868a5","message":"Merge branch 'zyc_sonicmoving' into 'MASTER'\r\n\r\n第三方绑定信息UI细节调整\r\n\r\n\r\n\r\nSee merge request !164","timestamp":"2017-04-10T16:33:04+08:00","url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android/commit/3340795ced978e7a6c86f26612c63a55efa868a5","author":{"name":"曾显状","email":"lynxz@soundnet.com.cn"},"added":[],"modified":["app/src/main/res/layout/activity_account_security.xml","app/src/main/res/values/strings.xml"],"removed":[]}] 25 | * total_commits_count : 3 26 | * repository : {"name":"sonicmoving-android","url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","description":"声动app andorid版本","homepage":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","git_http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git","git_ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","visibility_level":0} 27 | */ 28 | 29 | private String object_kind; 30 | private String before; 31 | private String after; 32 | private String ref; 33 | private String checkout_sha; 34 | private Object message; 35 | private long user_id; 36 | private String user_name; 37 | private String user_email; 38 | private String user_avatar; 39 | private long project_id; 40 | private ProjectBean project; 41 | private int total_commits_count; 42 | private RepositoryBean repository; 43 | private List commits; 44 | 45 | public String getObject_kind() { 46 | return object_kind; 47 | } 48 | 49 | public void setObject_kind(String object_kind) { 50 | this.object_kind = object_kind; 51 | } 52 | 53 | public String getBefore() { 54 | return before; 55 | } 56 | 57 | public void setBefore(String before) { 58 | this.before = before; 59 | } 60 | 61 | public String getAfter() { 62 | return after; 63 | } 64 | 65 | public void setAfter(String after) { 66 | this.after = after; 67 | } 68 | 69 | public String getRef() { 70 | return ref; 71 | } 72 | 73 | public void setRef(String ref) { 74 | this.ref = ref; 75 | } 76 | 77 | public String getCheckout_sha() { 78 | return checkout_sha; 79 | } 80 | 81 | public void setCheckout_sha(String checkout_sha) { 82 | this.checkout_sha = checkout_sha; 83 | } 84 | 85 | public Object getMessage() { 86 | return message; 87 | } 88 | 89 | public void setMessage(Object message) { 90 | this.message = message; 91 | } 92 | 93 | public long getUser_id() { 94 | return user_id; 95 | } 96 | 97 | public void setUser_id(long user_id) { 98 | this.user_id = user_id; 99 | } 100 | 101 | public String getUser_name() { 102 | return user_name; 103 | } 104 | 105 | public void setUser_name(String user_name) { 106 | this.user_name = user_name; 107 | } 108 | 109 | public String getUser_email() { 110 | return user_email; 111 | } 112 | 113 | public void setUser_email(String user_email) { 114 | this.user_email = user_email; 115 | } 116 | 117 | public String getUser_avatar() { 118 | return user_avatar; 119 | } 120 | 121 | public void setUser_avatar(String user_avatar) { 122 | this.user_avatar = user_avatar; 123 | } 124 | 125 | public long getProject_id() { 126 | return project_id; 127 | } 128 | 129 | public void setProject_id(long project_id) { 130 | this.project_id = project_id; 131 | } 132 | 133 | public ProjectBean getProject() { 134 | return project; 135 | } 136 | 137 | public void setProject(ProjectBean project) { 138 | this.project = project; 139 | } 140 | 141 | public int getTotal_commits_count() { 142 | return total_commits_count; 143 | } 144 | 145 | public void setTotal_commits_count(int total_commits_count) { 146 | this.total_commits_count = total_commits_count; 147 | } 148 | 149 | public RepositoryBean getRepository() { 150 | return repository; 151 | } 152 | 153 | public void setRepository(RepositoryBean repository) { 154 | this.repository = repository; 155 | } 156 | 157 | public List getCommits() { 158 | return commits; 159 | } 160 | 161 | public void setCommits(List commits) { 162 | this.commits = commits; 163 | } 164 | 165 | public static class ProjectBean { 166 | /** 167 | * name : sonicmoving-android 168 | * description : 声动app andorid版本 169 | * web_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 170 | * avatar_url : null 171 | * git_ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 172 | * git_http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 173 | * namespace : sonicmoving-app-team 174 | * visibility_level : 0 175 | * path_with_namespace : sonicmoving-app-team/sonicmoving-android 176 | * default_branch : MASTER 177 | * homepage : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 178 | * url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 179 | * ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 180 | * http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 181 | */ 182 | 183 | private String name; 184 | private String description; 185 | private String web_url; 186 | private Object avatar_url; 187 | private String git_ssh_url; 188 | private String git_http_url; 189 | private String namespace; 190 | private int visibility_level; 191 | private String path_with_namespace; 192 | private String default_branch; 193 | private String homepage; 194 | private String url; 195 | private String ssh_url; 196 | private String http_url; 197 | 198 | public String getName() { 199 | return name; 200 | } 201 | 202 | public void setName(String name) { 203 | this.name = name; 204 | } 205 | 206 | public String getDescription() { 207 | return description; 208 | } 209 | 210 | public void setDescription(String description) { 211 | this.description = description; 212 | } 213 | 214 | public String getWeb_url() { 215 | return web_url; 216 | } 217 | 218 | public void setWeb_url(String web_url) { 219 | this.web_url = web_url; 220 | } 221 | 222 | public Object getAvatar_url() { 223 | return avatar_url; 224 | } 225 | 226 | public void setAvatar_url(Object avatar_url) { 227 | this.avatar_url = avatar_url; 228 | } 229 | 230 | public String getGit_ssh_url() { 231 | return git_ssh_url; 232 | } 233 | 234 | public void setGit_ssh_url(String git_ssh_url) { 235 | this.git_ssh_url = git_ssh_url; 236 | } 237 | 238 | public String getGit_http_url() { 239 | return git_http_url; 240 | } 241 | 242 | public void setGit_http_url(String git_http_url) { 243 | this.git_http_url = git_http_url; 244 | } 245 | 246 | public String getNamespace() { 247 | return namespace; 248 | } 249 | 250 | public void setNamespace(String namespace) { 251 | this.namespace = namespace; 252 | } 253 | 254 | public int getVisibility_level() { 255 | return visibility_level; 256 | } 257 | 258 | public void setVisibility_level(int visibility_level) { 259 | this.visibility_level = visibility_level; 260 | } 261 | 262 | public String getPath_with_namespace() { 263 | return path_with_namespace; 264 | } 265 | 266 | public void setPath_with_namespace(String path_with_namespace) { 267 | this.path_with_namespace = path_with_namespace; 268 | } 269 | 270 | public String getDefault_branch() { 271 | return default_branch; 272 | } 273 | 274 | public void setDefault_branch(String default_branch) { 275 | this.default_branch = default_branch; 276 | } 277 | 278 | public String getHomepage() { 279 | return homepage; 280 | } 281 | 282 | public void setHomepage(String homepage) { 283 | this.homepage = homepage; 284 | } 285 | 286 | public String getUrl() { 287 | return url; 288 | } 289 | 290 | public void setUrl(String url) { 291 | this.url = url; 292 | } 293 | 294 | public String getSsh_url() { 295 | return ssh_url; 296 | } 297 | 298 | public void setSsh_url(String ssh_url) { 299 | this.ssh_url = ssh_url; 300 | } 301 | 302 | public String getHttp_url() { 303 | return http_url; 304 | } 305 | 306 | public void setHttp_url(String http_url) { 307 | this.http_url = http_url; 308 | } 309 | } 310 | 311 | public static class RepositoryBean { 312 | /** 313 | * name : sonicmoving-android 314 | * url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 315 | * description : 声动app andorid版本 316 | * homepage : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 317 | * git_http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 318 | * git_ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 319 | * visibility_level : 0 320 | */ 321 | 322 | private String name; 323 | private String url; 324 | private String description; 325 | private String homepage; 326 | private String git_http_url; 327 | private String git_ssh_url; 328 | private int visibility_level; 329 | 330 | public String getName() { 331 | return name; 332 | } 333 | 334 | public void setName(String name) { 335 | this.name = name; 336 | } 337 | 338 | public String getUrl() { 339 | return url; 340 | } 341 | 342 | public void setUrl(String url) { 343 | this.url = url; 344 | } 345 | 346 | public String getDescription() { 347 | return description; 348 | } 349 | 350 | public void setDescription(String description) { 351 | this.description = description; 352 | } 353 | 354 | public String getHomepage() { 355 | return homepage; 356 | } 357 | 358 | public void setHomepage(String homepage) { 359 | this.homepage = homepage; 360 | } 361 | 362 | public String getGit_http_url() { 363 | return git_http_url; 364 | } 365 | 366 | public void setGit_http_url(String git_http_url) { 367 | this.git_http_url = git_http_url; 368 | } 369 | 370 | public String getGit_ssh_url() { 371 | return git_ssh_url; 372 | } 373 | 374 | public void setGit_ssh_url(String git_ssh_url) { 375 | this.git_ssh_url = git_ssh_url; 376 | } 377 | 378 | public int getVisibility_level() { 379 | return visibility_level; 380 | } 381 | 382 | public void setVisibility_level(int visibility_level) { 383 | this.visibility_level = visibility_level; 384 | } 385 | } 386 | 387 | public static class CommitsBean { 388 | /** 389 | * id : 50f4f5d56e32c74959a4c03a23e18fedc20cea8f 390 | * message : 第三方绑定信息UI细节调整 391 | 392 | * timestamp : 2017-04-10T16:30:26+08:00 393 | * url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android/commit/50f4f5d56e32c74959a4c03a23e18fedc20cea8f 394 | * author : {"name":"zhuyichao","email":"97663202@qq.com"} 395 | * added : [] 396 | * modified : ["app/src/main/res/layout/activity_account_security.xml","app/src/main/res/values/strings.xml"] 397 | * removed : [] 398 | */ 399 | 400 | private String id; 401 | private String message; 402 | private String timestamp; 403 | private String url; 404 | private AuthorBean author; 405 | private List added; 406 | private List modified; 407 | private List removed; 408 | 409 | public String getId() { 410 | return id; 411 | } 412 | 413 | public void setId(String id) { 414 | this.id = id; 415 | } 416 | 417 | public String getMessage() { 418 | return message; 419 | } 420 | 421 | public void setMessage(String message) { 422 | this.message = message; 423 | } 424 | 425 | public String getTimestamp() { 426 | return timestamp; 427 | } 428 | 429 | public void setTimestamp(String timestamp) { 430 | this.timestamp = timestamp; 431 | } 432 | 433 | public String getUrl() { 434 | return url; 435 | } 436 | 437 | public void setUrl(String url) { 438 | this.url = url; 439 | } 440 | 441 | public AuthorBean getAuthor() { 442 | return author; 443 | } 444 | 445 | public void setAuthor(AuthorBean author) { 446 | this.author = author; 447 | } 448 | 449 | public List getAdded() { 450 | return added; 451 | } 452 | 453 | public void setAdded(List added) { 454 | this.added = added; 455 | } 456 | 457 | public List getModified() { 458 | return modified; 459 | } 460 | 461 | public void setModified(List modified) { 462 | this.modified = modified; 463 | } 464 | 465 | public List getRemoved() { 466 | return removed; 467 | } 468 | 469 | public void setRemoved(List removed) { 470 | this.removed = removed; 471 | } 472 | 473 | public static class AuthorBean { 474 | /** 475 | * name : zhuyichao 476 | * email : 97663202@qq.com 477 | */ 478 | 479 | private String name; 480 | private String email; 481 | 482 | public String getName() { 483 | return name; 484 | } 485 | 486 | public void setName(String name) { 487 | this.name = name; 488 | } 489 | 490 | public String getEmail() { 491 | return email; 492 | } 493 | 494 | public void setEmail(String email) { 495 | this.email = email; 496 | } 497 | } 498 | } 499 | } 500 | -------------------------------------------------------------------------------- /src/main/kotlin/org/lynxz/server/bean/GbMergeRequestEventBean.java: -------------------------------------------------------------------------------- 1 | package org.lynxz.server.bean; 2 | 3 | /** 4 | * Created by lynxz on 21/03/2017. 5 | * 实际的merge数据 6 | */ 7 | public class GbMergeRequestEventBean { 8 | 9 | /** 10 | * object_kind : merge_request 11 | * user : {"name":"真实姓名","username":"lynxz","avatar_url":"https://gitlab.soundbus.tech/uploads/user/avatar/31/lynxz_avatar.png"} 12 | * project : {"name":"sonicmoving-android","description":"声动app andorid项目","web_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","avatar_url":null,"git_ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","git_http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git","namespace":"sonicmoving-app-team","visibility_level":0,"path_with_namespace":"sonicmoving-app-team/sonicmoving-android","default_branch":"MASTER","homepage":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git"} 13 | * object_attributes : {"id":3204,"target_branch":"zxzWdf","source_branch":"zxzUlife","source_project_id":216,"author_id":31,"assignee_id":31,"title":"Zxz ulife","created_at":"2017-03-21 15:05:00 +0800","updated_at":"2017-03-21 15:05:00 +0800","milestone_id":null,"state":"opened","merge_status":"unchecked","target_project_id":216,"iid":95,"description":"","position":0,"locked_at":null,"updated_by_id":null,"merge_error":null,"merge_params":{},"merge_when_build_succeeds":false,"merge_user_id":null,"merge_commit_sha":null,"deleted_at":null,"source":{"name":"sonicmoving-android","description":"??????app andorid??????","web_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","avatar_url":null,"git_ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","git_http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git","namespace":"sonicmoving-app-team","visibility_level":0,"path_with_namespace":"sonicmoving-app-team/sonicmoving-android","default_branch":"MASTER","homepage":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git"},"target":{"name":"sonicmoving-android","description":"??????app andorid??????","web_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","avatar_url":null,"git_ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","git_http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git","namespace":"sonicmoving-app-team","visibility_level":0,"path_with_namespace":"sonicmoving-app-team/sonicmoving-android","default_branch":"MASTER","homepage":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git"},"last_commit":{"id":"71cf3cf59d33dd03b2bc34145dbea9b6e7444423","message":"??????hook\n","timestamp":"2017-03-21T14:54:36+08:00","url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android/commit/71cf3cf59d33dd03b2bc34145dbea9b6e7444423","author":{"name":"lynxz","email":"lynxz8866@gmail.com"}},"work_in_progress":false,"url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android/merge_requests/95","action":"open"} 14 | * repository : {"name":"sonicmoving-android","url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","description":"??????app andorid??????","homepage":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android"} 15 | * assignee : {"name":"真实姓名","username":"lynxz","avatar_url":"https://gitlab.soundbus.tech/uploads/user/avatar/31/lynxz_avatar.png"} 16 | */ 17 | 18 | private String object_kind; 19 | private UserBean user; 20 | private ProjectBean project; 21 | private ObjectAttributesBean object_attributes; 22 | private RepositoryBean repository; 23 | private AssigneeBean assignee; 24 | 25 | public String getObject_kind() { 26 | return object_kind; 27 | } 28 | 29 | public void setObject_kind(String object_kind) { 30 | this.object_kind = object_kind; 31 | } 32 | 33 | public UserBean getUser() { 34 | return user; 35 | } 36 | 37 | public void setUser(UserBean user) { 38 | this.user = user; 39 | } 40 | 41 | public ProjectBean getProject() { 42 | return project; 43 | } 44 | 45 | public void setProject(ProjectBean project) { 46 | this.project = project; 47 | } 48 | 49 | public ObjectAttributesBean getObject_attributes() { 50 | return object_attributes; 51 | } 52 | 53 | public void setObject_attributes(ObjectAttributesBean object_attributes) { 54 | this.object_attributes = object_attributes; 55 | } 56 | 57 | public RepositoryBean getRepository() { 58 | return repository; 59 | } 60 | 61 | public void setRepository(RepositoryBean repository) { 62 | this.repository = repository; 63 | } 64 | 65 | public AssigneeBean getAssignee() { 66 | return assignee; 67 | } 68 | 69 | public void setAssignee(AssigneeBean assignee) { 70 | this.assignee = assignee; 71 | } 72 | 73 | public static class UserBean { 74 | /** 75 | * name : 真实姓名 76 | * username : lynxz 77 | * avatar_url : https://gitlab.soundbus.tech/uploads/user/avatar/31/lynxz_avatar.png 78 | */ 79 | 80 | private String name; 81 | private String username; 82 | private String avatar_url; 83 | 84 | public String getName() { 85 | return name; 86 | } 87 | 88 | public void setName(String name) { 89 | this.name = name; 90 | } 91 | 92 | public String getUsername() { 93 | return username; 94 | } 95 | 96 | public void setUsername(String username) { 97 | this.username = username; 98 | } 99 | 100 | public String getAvatar_url() { 101 | return avatar_url; 102 | } 103 | 104 | public void setAvatar_url(String avatar_url) { 105 | this.avatar_url = avatar_url; 106 | } 107 | } 108 | 109 | public static class ProjectBean { 110 | /** 111 | * name : sonicmoving-android 112 | * description : 声动app andorid项目 113 | * web_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 114 | * avatar_url : null 115 | * git_ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 116 | * git_http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 117 | * namespace : sonicmoving-app-team 118 | * visibility_level : 0 119 | * path_with_namespace : sonicmoving-app-team/sonicmoving-android 120 | * default_branch : MASTER 121 | * homepage : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 122 | * url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 123 | * ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 124 | * http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 125 | */ 126 | 127 | private String name; 128 | private String description; 129 | private String web_url; 130 | private Object avatar_url; 131 | private String git_ssh_url; 132 | private String git_http_url; 133 | private String namespace; 134 | private int visibility_level; 135 | private String path_with_namespace; 136 | private String default_branch; 137 | private String homepage; 138 | private String url; 139 | private String ssh_url; 140 | private String http_url; 141 | 142 | public String getName() { 143 | return name; 144 | } 145 | 146 | public void setName(String name) { 147 | this.name = name; 148 | } 149 | 150 | public String getDescription() { 151 | return description; 152 | } 153 | 154 | public void setDescription(String description) { 155 | this.description = description; 156 | } 157 | 158 | public String getWeb_url() { 159 | return web_url; 160 | } 161 | 162 | public void setWeb_url(String web_url) { 163 | this.web_url = web_url; 164 | } 165 | 166 | public Object getAvatar_url() { 167 | return avatar_url; 168 | } 169 | 170 | public void setAvatar_url(Object avatar_url) { 171 | this.avatar_url = avatar_url; 172 | } 173 | 174 | public String getGit_ssh_url() { 175 | return git_ssh_url; 176 | } 177 | 178 | public void setGit_ssh_url(String git_ssh_url) { 179 | this.git_ssh_url = git_ssh_url; 180 | } 181 | 182 | public String getGit_http_url() { 183 | return git_http_url; 184 | } 185 | 186 | public void setGit_http_url(String git_http_url) { 187 | this.git_http_url = git_http_url; 188 | } 189 | 190 | public String getNamespace() { 191 | return namespace; 192 | } 193 | 194 | public void setNamespace(String namespace) { 195 | this.namespace = namespace; 196 | } 197 | 198 | public int getVisibility_level() { 199 | return visibility_level; 200 | } 201 | 202 | public void setVisibility_level(int visibility_level) { 203 | this.visibility_level = visibility_level; 204 | } 205 | 206 | public String getPath_with_namespace() { 207 | return path_with_namespace; 208 | } 209 | 210 | public void setPath_with_namespace(String path_with_namespace) { 211 | this.path_with_namespace = path_with_namespace; 212 | } 213 | 214 | public String getDefault_branch() { 215 | return default_branch; 216 | } 217 | 218 | public void setDefault_branch(String default_branch) { 219 | this.default_branch = default_branch; 220 | } 221 | 222 | public String getHomepage() { 223 | return homepage; 224 | } 225 | 226 | public void setHomepage(String homepage) { 227 | this.homepage = homepage; 228 | } 229 | 230 | public String getUrl() { 231 | return url; 232 | } 233 | 234 | public void setUrl(String url) { 235 | this.url = url; 236 | } 237 | 238 | public String getSsh_url() { 239 | return ssh_url; 240 | } 241 | 242 | public void setSsh_url(String ssh_url) { 243 | this.ssh_url = ssh_url; 244 | } 245 | 246 | public String getHttp_url() { 247 | return http_url; 248 | } 249 | 250 | public void setHttp_url(String http_url) { 251 | this.http_url = http_url; 252 | } 253 | } 254 | 255 | public static class ObjectAttributesBean { 256 | /** 257 | * id : 3204 258 | * target_branch : zxzWdf 259 | * source_branch : zxzUlife 260 | * source_project_id : 216 261 | * author_id : 31 262 | * assignee_id : 31 263 | * title : Zxz ulife 264 | * created_at : 2017-03-21 15:05:00 +0800 265 | * updated_at : 2017-03-21 15:05:00 +0800 266 | * milestone_id : null 267 | * state : opened 268 | * merge_status : unchecked 269 | * target_project_id : 216 270 | * iid : 95 271 | * description : 272 | * position : 0 273 | * locked_at : null 274 | * updated_by_id : null 275 | * merge_error : null 276 | * merge_params : {} 277 | * merge_when_build_succeeds : false 278 | * merge_user_id : null 279 | * merge_commit_sha : null 280 | * deleted_at : null 281 | * source : {"name":"sonicmoving-android","description":"??????app andorid??????","web_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","avatar_url":null,"git_ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","git_http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git","namespace":"sonicmoving-app-team","visibility_level":0,"path_with_namespace":"sonicmoving-app-team/sonicmoving-android","default_branch":"MASTER","homepage":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git"} 282 | * target : {"name":"sonicmoving-android","description":"??????app andorid??????","web_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","avatar_url":null,"git_ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","git_http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git","namespace":"sonicmoving-app-team","visibility_level":0,"path_with_namespace":"sonicmoving-app-team/sonicmoving-android","default_branch":"MASTER","homepage":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android","url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","ssh_url":"git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git","http_url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git"} 283 | * last_commit : {"id":"71cf3cf59d33dd03b2bc34145dbea9b6e7444423","message":"??????hook\n","timestamp":"2017-03-21T14:54:36+08:00","url":"https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android/commit/71cf3cf59d33dd03b2bc34145dbea9b6e7444423","author":{"name":"lynxz","email":"lynxz8866@gmail.com"}} 284 | * work_in_progress : false 285 | * url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android/merge_requests/95 286 | * action : open 287 | */ 288 | 289 | private int id; 290 | private String target_branch; 291 | private String source_branch; 292 | private int source_project_id; 293 | private int author_id; 294 | private int assignee_id; 295 | private String title; 296 | private String created_at; 297 | private String updated_at; 298 | private Object milestone_id; 299 | private String state; 300 | private String merge_status; 301 | private int target_project_id; 302 | private int iid; 303 | private String description; 304 | private int position; 305 | private Object locked_at; 306 | private Object updated_by_id; 307 | private Object merge_error; 308 | private MergeParamsBean merge_params; 309 | private boolean merge_when_build_succeeds; 310 | private Object merge_user_id; 311 | private String merge_commit_sha; 312 | private Object deleted_at; 313 | private SourceBean source; 314 | private TargetBean target; 315 | private LastCommitBean last_commit; 316 | private boolean work_in_progress; 317 | private String url; 318 | private String action; 319 | 320 | public int getId() { 321 | return id; 322 | } 323 | 324 | public void setId(int id) { 325 | this.id = id; 326 | } 327 | 328 | public String getTarget_branch() { 329 | return target_branch; 330 | } 331 | 332 | public void setTarget_branch(String target_branch) { 333 | this.target_branch = target_branch; 334 | } 335 | 336 | public String getSource_branch() { 337 | return source_branch; 338 | } 339 | 340 | public void setSource_branch(String source_branch) { 341 | this.source_branch = source_branch; 342 | } 343 | 344 | public int getSource_project_id() { 345 | return source_project_id; 346 | } 347 | 348 | public void setSource_project_id(int source_project_id) { 349 | this.source_project_id = source_project_id; 350 | } 351 | 352 | public int getAuthor_id() { 353 | return author_id; 354 | } 355 | 356 | public void setAuthor_id(int author_id) { 357 | this.author_id = author_id; 358 | } 359 | 360 | public int getAssignee_id() { 361 | return assignee_id; 362 | } 363 | 364 | public void setAssignee_id(int assignee_id) { 365 | this.assignee_id = assignee_id; 366 | } 367 | 368 | public String getTitle() { 369 | return title; 370 | } 371 | 372 | public void setTitle(String title) { 373 | this.title = title; 374 | } 375 | 376 | public String getCreated_at() { 377 | return created_at; 378 | } 379 | 380 | public void setCreated_at(String created_at) { 381 | this.created_at = created_at; 382 | } 383 | 384 | public String getUpdated_at() { 385 | return updated_at; 386 | } 387 | 388 | public void setUpdated_at(String updated_at) { 389 | this.updated_at = updated_at; 390 | } 391 | 392 | public Object getMilestone_id() { 393 | return milestone_id; 394 | } 395 | 396 | public void setMilestone_id(Object milestone_id) { 397 | this.milestone_id = milestone_id; 398 | } 399 | 400 | public String getState() { 401 | return state; 402 | } 403 | 404 | public void setState(String state) { 405 | this.state = state; 406 | } 407 | 408 | public String getMerge_status() { 409 | return merge_status; 410 | } 411 | 412 | public void setMerge_status(String merge_status) { 413 | this.merge_status = merge_status; 414 | } 415 | 416 | public int getTarget_project_id() { 417 | return target_project_id; 418 | } 419 | 420 | public void setTarget_project_id(int target_project_id) { 421 | this.target_project_id = target_project_id; 422 | } 423 | 424 | public int getIid() { 425 | return iid; 426 | } 427 | 428 | public void setIid(int iid) { 429 | this.iid = iid; 430 | } 431 | 432 | public String getDescription() { 433 | return description; 434 | } 435 | 436 | public void setDescription(String description) { 437 | this.description = description; 438 | } 439 | 440 | public int getPosition() { 441 | return position; 442 | } 443 | 444 | public void setPosition(int position) { 445 | this.position = position; 446 | } 447 | 448 | public Object getLocked_at() { 449 | return locked_at; 450 | } 451 | 452 | public void setLocked_at(Object locked_at) { 453 | this.locked_at = locked_at; 454 | } 455 | 456 | public Object getUpdated_by_id() { 457 | return updated_by_id; 458 | } 459 | 460 | public void setUpdated_by_id(Object updated_by_id) { 461 | this.updated_by_id = updated_by_id; 462 | } 463 | 464 | public Object getMerge_error() { 465 | return merge_error; 466 | } 467 | 468 | public void setMerge_error(Object merge_error) { 469 | this.merge_error = merge_error; 470 | } 471 | 472 | public MergeParamsBean getMerge_params() { 473 | return merge_params; 474 | } 475 | 476 | public void setMerge_params(MergeParamsBean merge_params) { 477 | this.merge_params = merge_params; 478 | } 479 | 480 | public boolean isMerge_when_build_succeeds() { 481 | return merge_when_build_succeeds; 482 | } 483 | 484 | public void setMerge_when_build_succeeds(boolean merge_when_build_succeeds) { 485 | this.merge_when_build_succeeds = merge_when_build_succeeds; 486 | } 487 | 488 | public Object getMerge_user_id() { 489 | return merge_user_id; 490 | } 491 | 492 | public void setMerge_user_id(Object merge_user_id) { 493 | this.merge_user_id = merge_user_id; 494 | } 495 | 496 | public String getMerge_commit_sha() { 497 | return merge_commit_sha; 498 | } 499 | 500 | public void setMerge_commit_sha(String merge_commit_sha) { 501 | this.merge_commit_sha = merge_commit_sha; 502 | } 503 | 504 | public Object getDeleted_at() { 505 | return deleted_at; 506 | } 507 | 508 | public void setDeleted_at(Object deleted_at) { 509 | this.deleted_at = deleted_at; 510 | } 511 | 512 | public SourceBean getSource() { 513 | return source; 514 | } 515 | 516 | public void setSource(SourceBean source) { 517 | this.source = source; 518 | } 519 | 520 | public TargetBean getTarget() { 521 | return target; 522 | } 523 | 524 | public void setTarget(TargetBean target) { 525 | this.target = target; 526 | } 527 | 528 | public LastCommitBean getLast_commit() { 529 | return last_commit; 530 | } 531 | 532 | public void setLast_commit(LastCommitBean last_commit) { 533 | this.last_commit = last_commit; 534 | } 535 | 536 | public boolean isWork_in_progress() { 537 | return work_in_progress; 538 | } 539 | 540 | public void setWork_in_progress(boolean work_in_progress) { 541 | this.work_in_progress = work_in_progress; 542 | } 543 | 544 | public String getUrl() { 545 | return url; 546 | } 547 | 548 | public void setUrl(String url) { 549 | this.url = url; 550 | } 551 | 552 | public String getAction() { 553 | return action; 554 | } 555 | 556 | public void setAction(String action) { 557 | this.action = action; 558 | } 559 | 560 | public static class MergeParamsBean { 561 | } 562 | 563 | public static class SourceBean { 564 | /** 565 | * name : sonicmoving-android 566 | * description : ??????app andorid?????? 567 | * web_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 568 | * avatar_url : null 569 | * git_ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 570 | * git_http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 571 | * namespace : sonicmoving-app-team 572 | * visibility_level : 0 573 | * path_with_namespace : sonicmoving-app-team/sonicmoving-android 574 | * default_branch : MASTER 575 | * homepage : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 576 | * url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 577 | * ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 578 | * http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 579 | */ 580 | 581 | private String name; 582 | private String description; 583 | private String web_url; 584 | private Object avatar_url; 585 | private String git_ssh_url; 586 | private String git_http_url; 587 | private String namespace; 588 | private int visibility_level; 589 | private String path_with_namespace; 590 | private String default_branch; 591 | private String homepage; 592 | private String url; 593 | private String ssh_url; 594 | private String http_url; 595 | 596 | public String getName() { 597 | return name; 598 | } 599 | 600 | public void setName(String name) { 601 | this.name = name; 602 | } 603 | 604 | public String getDescription() { 605 | return description; 606 | } 607 | 608 | public void setDescription(String description) { 609 | this.description = description; 610 | } 611 | 612 | public String getWeb_url() { 613 | return web_url; 614 | } 615 | 616 | public void setWeb_url(String web_url) { 617 | this.web_url = web_url; 618 | } 619 | 620 | public Object getAvatar_url() { 621 | return avatar_url; 622 | } 623 | 624 | public void setAvatar_url(Object avatar_url) { 625 | this.avatar_url = avatar_url; 626 | } 627 | 628 | public String getGit_ssh_url() { 629 | return git_ssh_url; 630 | } 631 | 632 | public void setGit_ssh_url(String git_ssh_url) { 633 | this.git_ssh_url = git_ssh_url; 634 | } 635 | 636 | public String getGit_http_url() { 637 | return git_http_url; 638 | } 639 | 640 | public void setGit_http_url(String git_http_url) { 641 | this.git_http_url = git_http_url; 642 | } 643 | 644 | public String getNamespace() { 645 | return namespace; 646 | } 647 | 648 | public void setNamespace(String namespace) { 649 | this.namespace = namespace; 650 | } 651 | 652 | public int getVisibility_level() { 653 | return visibility_level; 654 | } 655 | 656 | public void setVisibility_level(int visibility_level) { 657 | this.visibility_level = visibility_level; 658 | } 659 | 660 | public String getPath_with_namespace() { 661 | return path_with_namespace; 662 | } 663 | 664 | public void setPath_with_namespace(String path_with_namespace) { 665 | this.path_with_namespace = path_with_namespace; 666 | } 667 | 668 | public String getDefault_branch() { 669 | return default_branch; 670 | } 671 | 672 | public void setDefault_branch(String default_branch) { 673 | this.default_branch = default_branch; 674 | } 675 | 676 | public String getHomepage() { 677 | return homepage; 678 | } 679 | 680 | public void setHomepage(String homepage) { 681 | this.homepage = homepage; 682 | } 683 | 684 | public String getUrl() { 685 | return url; 686 | } 687 | 688 | public void setUrl(String url) { 689 | this.url = url; 690 | } 691 | 692 | public String getSsh_url() { 693 | return ssh_url; 694 | } 695 | 696 | public void setSsh_url(String ssh_url) { 697 | this.ssh_url = ssh_url; 698 | } 699 | 700 | public String getHttp_url() { 701 | return http_url; 702 | } 703 | 704 | public void setHttp_url(String http_url) { 705 | this.http_url = http_url; 706 | } 707 | } 708 | 709 | public static class TargetBean { 710 | /** 711 | * name : sonicmoving-android 712 | * description : ??????app andorid?????? 713 | * web_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 714 | * avatar_url : null 715 | * git_ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 716 | * git_http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 717 | * namespace : sonicmoving-app-team 718 | * visibility_level : 0 719 | * path_with_namespace : sonicmoving-app-team/sonicmoving-android 720 | * default_branch : MASTER 721 | * homepage : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 722 | * url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 723 | * ssh_url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 724 | * http_url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android.git 725 | */ 726 | 727 | private String name; 728 | private String description; 729 | private String web_url; 730 | private Object avatar_url; 731 | private String git_ssh_url; 732 | private String git_http_url; 733 | private String namespace; 734 | private int visibility_level; 735 | private String path_with_namespace; 736 | private String default_branch; 737 | private String homepage; 738 | private String url; 739 | private String ssh_url; 740 | private String http_url; 741 | 742 | public String getName() { 743 | return name; 744 | } 745 | 746 | public void setName(String name) { 747 | this.name = name; 748 | } 749 | 750 | public String getDescription() { 751 | return description; 752 | } 753 | 754 | public void setDescription(String description) { 755 | this.description = description; 756 | } 757 | 758 | public String getWeb_url() { 759 | return web_url; 760 | } 761 | 762 | public void setWeb_url(String web_url) { 763 | this.web_url = web_url; 764 | } 765 | 766 | public Object getAvatar_url() { 767 | return avatar_url; 768 | } 769 | 770 | public void setAvatar_url(Object avatar_url) { 771 | this.avatar_url = avatar_url; 772 | } 773 | 774 | public String getGit_ssh_url() { 775 | return git_ssh_url; 776 | } 777 | 778 | public void setGit_ssh_url(String git_ssh_url) { 779 | this.git_ssh_url = git_ssh_url; 780 | } 781 | 782 | public String getGit_http_url() { 783 | return git_http_url; 784 | } 785 | 786 | public void setGit_http_url(String git_http_url) { 787 | this.git_http_url = git_http_url; 788 | } 789 | 790 | public String getNamespace() { 791 | return namespace; 792 | } 793 | 794 | public void setNamespace(String namespace) { 795 | this.namespace = namespace; 796 | } 797 | 798 | public int getVisibility_level() { 799 | return visibility_level; 800 | } 801 | 802 | public void setVisibility_level(int visibility_level) { 803 | this.visibility_level = visibility_level; 804 | } 805 | 806 | public String getPath_with_namespace() { 807 | return path_with_namespace; 808 | } 809 | 810 | public void setPath_with_namespace(String path_with_namespace) { 811 | this.path_with_namespace = path_with_namespace; 812 | } 813 | 814 | public String getDefault_branch() { 815 | return default_branch; 816 | } 817 | 818 | public void setDefault_branch(String default_branch) { 819 | this.default_branch = default_branch; 820 | } 821 | 822 | public String getHomepage() { 823 | return homepage; 824 | } 825 | 826 | public void setHomepage(String homepage) { 827 | this.homepage = homepage; 828 | } 829 | 830 | public String getUrl() { 831 | return url; 832 | } 833 | 834 | public void setUrl(String url) { 835 | this.url = url; 836 | } 837 | 838 | public String getSsh_url() { 839 | return ssh_url; 840 | } 841 | 842 | public void setSsh_url(String ssh_url) { 843 | this.ssh_url = ssh_url; 844 | } 845 | 846 | public String getHttp_url() { 847 | return http_url; 848 | } 849 | 850 | public void setHttp_url(String http_url) { 851 | this.http_url = http_url; 852 | } 853 | } 854 | 855 | public static class LastCommitBean { 856 | /** 857 | * id : 71cf3cf59d33dd03b2bc34145dbea9b6e7444423 858 | * message : ??????hook 859 | *

860 | * timestamp : 2017-03-21T14:54:36+08:00 861 | * url : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android/commit/71cf3cf59d33dd03b2bc34145dbea9b6e7444423 862 | * author : {"name":"lynxz","email":"lynxz8866@gmail.com"} 863 | */ 864 | 865 | private String id; 866 | private String message; 867 | private String timestamp; 868 | private String url; 869 | private AuthorBean author; 870 | 871 | public String getId() { 872 | return id; 873 | } 874 | 875 | public void setId(String id) { 876 | this.id = id; 877 | } 878 | 879 | public String getMessage() { 880 | return message; 881 | } 882 | 883 | public void setMessage(String message) { 884 | this.message = message; 885 | } 886 | 887 | public String getTimestamp() { 888 | return timestamp; 889 | } 890 | 891 | public void setTimestamp(String timestamp) { 892 | this.timestamp = timestamp; 893 | } 894 | 895 | public String getUrl() { 896 | return url; 897 | } 898 | 899 | public void setUrl(String url) { 900 | this.url = url; 901 | } 902 | 903 | public AuthorBean getAuthor() { 904 | return author; 905 | } 906 | 907 | public void setAuthor(AuthorBean author) { 908 | this.author = author; 909 | } 910 | 911 | public static class AuthorBean { 912 | /** 913 | * name : lynxz 914 | * email : lynxz8866@gmail.com 915 | */ 916 | 917 | private String name; 918 | private String email; 919 | 920 | public String getName() { 921 | return name; 922 | } 923 | 924 | public void setName(String name) { 925 | this.name = name; 926 | } 927 | 928 | public String getEmail() { 929 | return email; 930 | } 931 | 932 | public void setEmail(String email) { 933 | this.email = email; 934 | } 935 | } 936 | } 937 | } 938 | 939 | public static class RepositoryBean { 940 | /** 941 | * name : sonicmoving-android 942 | * url : git@gitlab.soundbus.tech:sonicmoving-app-team/sonicmoving-android.git 943 | * description : ??????app andorid?????? 944 | * homepage : https://gitlab.soundbus.tech/sonicmoving-app-team/sonicmoving-android 945 | */ 946 | 947 | private String name; 948 | private String url; 949 | private String description; 950 | private String homepage; 951 | 952 | public String getName() { 953 | return name; 954 | } 955 | 956 | public void setName(String name) { 957 | this.name = name; 958 | } 959 | 960 | public String getUrl() { 961 | return url; 962 | } 963 | 964 | public void setUrl(String url) { 965 | this.url = url; 966 | } 967 | 968 | public String getDescription() { 969 | return description; 970 | } 971 | 972 | public void setDescription(String description) { 973 | this.description = description; 974 | } 975 | 976 | public String getHomepage() { 977 | return homepage; 978 | } 979 | 980 | public void setHomepage(String homepage) { 981 | this.homepage = homepage; 982 | } 983 | } 984 | 985 | public static class AssigneeBean { 986 | /** 987 | * name : 真实姓名 988 | * username : lynxz 989 | * avatar_url : https://gitlab.soundbus.tech/uploads/user/avatar/31/lynxz_avatar.png 990 | */ 991 | 992 | private String name; 993 | private String username; 994 | private String avatar_url; 995 | 996 | public String getName() { 997 | return name; 998 | } 999 | 1000 | public void setName(String name) { 1001 | this.name = name; 1002 | } 1003 | 1004 | public String getUsername() { 1005 | return username; 1006 | } 1007 | 1008 | public void setUsername(String username) { 1009 | this.username = username; 1010 | } 1011 | 1012 | public String getAvatar_url() { 1013 | return avatar_url; 1014 | } 1015 | 1016 | public void setAvatar_url(String avatar_url) { 1017 | this.avatar_url = avatar_url; 1018 | } 1019 | } 1020 | } 1021 | --------------------------------------------------------------------------------