初始化

This commit is contained in:
jimlee
2023-01-10 16:52:33 +08:00
parent 78fa2be0de
commit c7633de700
17 changed files with 2784 additions and 1 deletions

4
.gitignore vendored
View File

@@ -23,7 +23,9 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
replay_pid* replay_pid*
.iml *.iml
target
logs
**/target **/target
**/logs **/logs
*.sh *.sh

View File

@@ -0,0 +1,23 @@
package com.pomelotea.hoperun.sign
import org.slf4j.LoggerFactory
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import java.text.SimpleDateFormat
import java.util.*
/**
*
* @version 0.0.1
* @author jimlee
* date 2022-07-15 10:53
* 启动入口
**/
@SpringBootApplication
open class DakaApplication {
}
private val logger = LoggerFactory.getLogger("APPLICATION-STARTER")
fun main(args: Array<String>) {
SpringApplication.run(DakaApplication::class.java, *args)
logger.info("hoperun打卡服务启动成功:{}", SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(Date()))
}

View File

@@ -0,0 +1,433 @@
package com.pomelotea.hoperun.sign.api
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONArray
import com.alibaba.fastjson.JSONObject
import com.alibaba.fastjson.TypeReference
import com.pomelotea.hoperun.sign.config.HoperunUserConfig
import com.pomelotea.hoperun.sign.config.UserConfig
import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.text.SimpleDateFormat
import java.time.Duration
import java.time.LocalDate
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
/**
*
* @version 0.0.1
* @author jimlee
* date 2022-07-15 11:01
* hoperun打卡服务接口
**/
@RestController
@RequestMapping("/api/daka")
class HoperunSignController(
private val hoperunUserConfig: HoperunUserConfig
) {
companion object {
const val DAKA_URL = "http://pom.hoperun.com:8187/attm/attence/recordAttendance"
const val MONTH_ATT_URL = "http://pom.hoperun.com:8187/attm/calendar/monthAtt"
const val LOGIN_URL = "http://pom.hoperun.com:8187/attm/login/login"
val client = OkHttpClient()
.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.callTimeout(Duration.ofSeconds(10))
// .addInterceptor(LogInterceptor())
.build()
val sessionMap: MutableMap<String, String?> = HashMap()
val expireMap: MutableMap<String, Long> = HashMap()
fun getNowDateyyyy_MM(): String {
val localDate = LocalDate.now()
return "${localDate.year}-${pad(localDate.monthValue)}"
}
fun getLastDateyyyy_MM(): String {
val localDate = LocalDate.now().minusMonths(1)
return "${localDate.year}-${pad(localDate.monthValue)}"
}
fun getNowDateyyyy_MM_dd(): String {
val localDate = LocalDate.now()
return "${localDate.year}-${pad(localDate.monthValue)}-${pad(localDate.dayOfMonth)}"
}
private fun pad(num: Int): String {
return if (num < 10) "0$num" else "$num"
}
}
@GetMapping("/username/{employeeNo}")
fun getUsername(@PathVariable employeeNo: String): WebResult<LoginResponse?> {
val userConfig = getUserConfig(employeeNo)
if (userConfig == null) {
return WebResult.getFailed("登陆失败")
}
return WebResult.getSuccess(
LoginResponse(
userConfig.username,
userConfig.device,
userConfig.project_id,
userConfig.projectname,
"杭州市"
)
)
}
@GetMapping("/last/{employeeNo}")
fun getLast5DaysRecord(@PathVariable employeeNo: String): WebResult<Any?> {
val jsessionId = getJsessionIdAutoLogin(employeeNo) ?: return WebResult.getFailed("登陆失败")
return WebResult.getSuccess(monthAtt(employeeNo, jsessionId))
}
@PostMapping("/endTime")
fun endTime(@RequestBody request: DakaRequest): WebResult<Any?> {
val userConfig = hoperunUserConfig.userConfigMap.get(request.employeeNo)
if (userConfig?.device == null) {
return WebResult.getFailed("用户没有配置的deviceUA")
}
val jsessionId = getJsessionIdAutoLogin(request.employeeNo)
if (jsessionId == null) {
return WebResult.getFailed("登陆失败")
}
val date = if (request.date == "今天") SimpleDateFormat("yyyy-MM-dd").format(Date()) else request.date
val dakaRequest = Request.Builder()
.url(DAKA_URL)
.post(
JSON.toJSONString(
endTimeHoperunDakaRequest(
request.employeeNo,
date,
request.time
)
).toRequestBody("application/json;charset=utf-8".toMediaTypeOrNull())
)
.addHeader("Cookie", "JSESSIONID=$jsessionId")
.build()
val result: String? = client.newCall(dakaRequest).execute().body?.string()
return WebResult.getSuccess(JSONObject.parseObject(result, DakaResponse::class.java))
}
@PostMapping("/beginTime")
fun beginTime(@RequestBody request: DakaRequest): WebResult<Any?> {
val userConfig = hoperunUserConfig.userConfigMap.get(request.employeeNo)
if (userConfig?.device == null) {
return WebResult.getFailed("用户没有配置的deviceUA")
}
val jsessionId = getJsessionIdAutoLogin(request.employeeNo)
if (jsessionId == null) {
return WebResult.getFailed("登陆失败")
}
val date = if (request.date == "今天") SimpleDateFormat("yyyy-MM-dd").format(Date()) else request.date
val dakaRequest = Request.Builder()
.url(DAKA_URL)
.post(
JSON.toJSONString(
beginTimeHoperunDakaRequest(
request.employeeNo,
date,
request.time
)
).toRequestBody("application/json;charset=utf-8".toMediaTypeOrNull())
)
.addHeader("Cookie", "JSESSIONID=$jsessionId")
.build()
val result: String? = client.newCall(dakaRequest).execute().body?.string()
return WebResult.getSuccess(JSONObject.parseObject(result, DakaResponse::class.java))
}
private fun getJsessionIdAutoLogin(employeeNo: String): String? {
if (sessionMap.get(employeeNo) == null || expireMap.get(employeeNo) == null || expireMap.get(employeeNo)!! < System.currentTimeMillis()) {
login(employeeNo)
}
return sessionMap.get(employeeNo)
}
private fun getUsernameAutoLogin(employeeNo: String): String? {
val userConfig = hoperunUserConfig.userConfigMap.get(employeeNo)
if (userConfig?.username == null) {
login(employeeNo)
}
return hoperunUserConfig.userConfigMap.get(employeeNo)?.username
}
private fun getUserConfig(employeeNo: String): UserConfig? {
val userConfig = hoperunUserConfig.userConfigMap.get(employeeNo)
if (userConfig?.username == null) {
login(employeeNo)
}
return hoperunUserConfig.userConfigMap.get(employeeNo)
}
/**
* 查询近两个月的
*/
private fun monthAtt(employeeNo: String, jsessionId: String): List<MonthAttLog> {
val monthAttResult: MutableList<MonthAttLog> = ArrayList()
val monthAttList: List<MonthAttLog> = queryMonthAttData(employeeNo, jsessionId, getNowDateyyyy_MM() + "-01")
// 如果dateType = 1的结果小于3条查询上月
val monthAttLogs = monthAttList.sortedByDescending { it.yearmonth }.filter { it.dateType == "1" }
.filter { it.yearmonth!!.compareTo(getNowDateyyyy_MM_dd()) <= 0 }
monthAttResult.addAll(monthAttLogs)
val lastMonthAttList: List<MonthAttLog> =
queryMonthAttData(employeeNo, jsessionId, getLastDateyyyy_MM() + "-01")
val lastMonthAttLogs = lastMonthAttList.sortedByDescending { it.yearmonth }.filter { it.dateType == "1" }
monthAttResult.addAll(lastMonthAttLogs)
return monthAttResult
}
private fun queryMonthAttData(employeeNo: String, jsessionId: String, yearmonth: String): List<MonthAttLog> {
val monthAttRequest = Request.Builder()
.url(MONTH_ATT_URL)
.post(
FormBody.Builder()
.add("staff_code", padEmployeeNumber(employeeNo))
.add("yearmonth", yearmonth)
.build()
)
.addHeader("Cookie", "JSESSIONID=$jsessionId")
.build()
val result: String? = client.newCall(monthAttRequest).execute().body?.string()
return JSONObject.parseObject(
JSONObject.parseObject(result).getString("data"),
object : TypeReference<List<MonthAttLog>?>() {})
}
private fun login(employeeNo: String) {
val jsessionId: String?
val loginRequest = Request.Builder()
.url(LOGIN_URL)
.post(
FormBody.Builder()
.add("login_id", padEmployeeNumber(employeeNo))
.add("password", "123456")
.build()
)
.build()
val response = client.newCall(loginRequest).execute()
jsessionId = response.request.url.pathSegments[2].substring(19)
sessionMap.put(employeeNo, jsessionId)
expireMap.put(employeeNo, System.currentTimeMillis() + 300000)
// 读取员工姓名
if (hoperunUserConfig.userConfigMap[employeeNo]?.username == null) {
setUserConfig(employeeNo)
}
}
private fun setUserConfig(employeeNo: String) {
// 获取deviceua
val loginRequest = Request.Builder()
.url("http://pom.hoperun.com:8187/attp/login/login.do")
.post(
JSON.toJSONString(
mapOf(
"login_id" to padEmployeeNumber(employeeNo),
"password" to "123456",
"roleType" to "0"
)
).toRequestBody("application/json;charset=utf-8".toMediaTypeOrNull())
)
.build()
val response = client.newCall(loginRequest).execute()
val jsessionId = response.headers("Set-Cookie")[0].substring(11, 43)
val attendancesDetailRequest = Request.Builder()
.url("http://pom.hoperun.com:8187/attp/attendances/queryAttendancesDetail")
.post(
JSON.toJSONString(
mapOf(
"beginDate" to getLastDateyyyy_MM() + "-21",
"endDate" to getNowDateyyyy_MM_dd(),
"staffCode" to padEmployeeNumber(employeeNo)
)
).toRequestBody("application/json;charset=utf-8".toMediaTypeOrNull())
)
.addHeader("Cookie", "JSESSIONID=$jsessionId")
.build()
val attendancesDetailResponse = client.newCall(attendancesDetailRequest).execute()
val bodyString = attendancesDetailResponse.body?.string()
val dakaJsonArray = JSONObject.parseArray(bodyString)
val dakaInfo = dakaJsonArray.getJSONObject(0)
val dakaList = dakaInfo.getJSONArray("list")
var lastDakaInfo = dakaList.getJSONObject(dakaList.size - 1)
val userConfig: UserConfig = hoperunUserConfig.userConfigMap.get(employeeNo) ?: UserConfig()
if (lastDakaInfo.getString("begin_time") == null) {
for (i in dakaList.size - 1 downTo 0) {
lastDakaInfo = dakaList.getJSONObject(i)
if (lastDakaInfo.getString("actual_area_end") != null) {
break
}
}
}
val username: String = lastDakaInfo.getString("staff_name")
userConfig.username = username
if (userConfig.device == null) {
if (lastDakaInfo.getString("actual_area_end") != null) {
val area: String = lastDakaInfo.getString("actual_area_end")
userConfig.device = area.substring(area.lastIndexOf("Qing") + 13)
} else {
userConfig.device = null
}
}
if (userConfig.projectcode == null) {
userConfig.projectcode = lastDakaInfo.getString("project_id")
}
if (userConfig.projectname == null) {
userConfig.projectname = lastDakaInfo.getString("projectname")
}
if (userConfig.project_id == null) {
userConfig.project_id = lastDakaInfo.getString("project_id")
}
hoperunUserConfig.addUserConfig(
employeeNo, userConfig
)
}
private fun beginTimeHoperunDakaRequest(
employeeNo: String,
yearmonth: String,
begin_time: String?
): HoperunDakaRequest {
val ua = hoperunUserConfig.getUA(employeeNo)
val userConfig: UserConfig = getUserConfig(employeeNo)!!
val hoperunDakaRequest = HoperunDakaRequest(
staff_code = padEmployeeNumber(employeeNo),
yearmonth = yearmonth,
userConfig.project_id!!,
userConfig.projectname!!,
userConfig.projectcode!!,
actualArea = ua
)
hoperunDakaRequest.begin_time = begin_time
return hoperunDakaRequest
}
private fun endTimeHoperunDakaRequest(
employeeNo: String,
yearmonth: String,
end_time: String?
): HoperunDakaRequest {
val ua = hoperunUserConfig.getUA(employeeNo)
val userConfig: UserConfig = getUserConfig(employeeNo)!!
val hoperunDakaRequest = HoperunDakaRequest(
staff_code = padEmployeeNumber(employeeNo),
yearmonth = yearmonth,
userConfig.project_id!!,
userConfig.projectname!!,
userConfig.projectcode!!,
actualArea = ua
)
hoperunDakaRequest.end_time = end_time
return hoperunDakaRequest
}
fun padEmployeeNumber(employeeNumber: String): String {
return when (employeeNumber.length) {
5 -> "0000" + employeeNumber
4 -> "00000" + employeeNumber
else -> employeeNumber
}
}
}
data class DakaRequest(
val employeeNo: String,
val date: String,
val time: String
)
data class DakaResponse(
var result: String? = null,
var comment: String? = null,
var data: String? = null
)
class WebResult<T> protected constructor() : java.io.Serializable {
var data: T? = null
var code: Int? = null
var success = false
var message: String? = null
@Suppress("UNUSED")
var timestamp = System.currentTimeMillis()
companion object {
fun <T> getSuccess(data: T): WebResult<T> {
return getWebResult(true, data, "success", 0)
}
fun <T> getFailed(message: String = "failed"): WebResult<T?> {
return getWebResult(false, null, message, -1)
}
fun <T> getWebResult(success: Boolean, data: T, message: String?, code: Int?): WebResult<T> {
val webResult = WebResult<T>()
webResult.success = success
webResult.data = data
webResult.code = code
webResult.message = message
return webResult
}
}
}
data class MonthAttLog(
var area_id: String? = null,
var area_id_begin: String? = null,
var area_id_end: String? = null,
var attState: String? = null,
var att_type: String? = null,
var begin_time: String? = null,
var dateType: String? = null,
var departmentcode: String? = null,
var end_time: String? = null,
var project_id: String? = null,
var projectcode: String? = null,
var staff_code: String? = null,
var yearmonth: String? = null
)
data class HoperunDakaRequest(
val staff_code: String,
val yearmonth: String,
var project_id: String = "U2103S000078",
var projectname: String = "JRKF-银河资产对接合作平台贷项目",
var projectcode: String = "U2103S000078",
val area_id: String = "杭州市",
val actualArea: String,
var begin_time: String? = null,
var end_time: String? = null
)
data class LoginResponse(
var username: String? = null,
var device: String? = null,
var project_id: String? = null,
var projectname: String? = null,
var area: String? = null
)

View File

@@ -0,0 +1,59 @@
package com.pomelotea.hoperun.sign.config
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
/**
*
* @version 0.0.1
* @author jimlee
* date 2022-07-18 09:56
* 用户配置
**/
@Configuration
@ConfigurationProperties("hoperun")
open class HoperunUserConfig {
val userConfigMap: MutableMap<String, UserConfig> = HashMap()
var address: String = "浙江省杭州市西湖区万塘路18号黄龙时代广场B座"
var longitueHead: String = "120.131"
var latitudeHead: String = "30.279"
var longitueShort: String = "120.136679"
var latitudeShort: String = "30.279766"
var qingUa: String = "Qing/0.9.101"
fun getUA(emplotyeeNo: String): String {
return address +
"$longitueHead${random(11)}," +
"$latitudeHead${random(12)};" +
"$longitueShort," +
"$latitudeShort;" +
"$qingUa;" +
(userConfigMap.get(emplotyeeNo)!!.device ?: "")
}
fun addUserConfig(emplotyeeNo: String, userConfig: UserConfig) {
userConfigMap.put(emplotyeeNo, userConfig)
}
fun random(place: Int): Long {
var random = 0L
var index = 1
var divisor = 1
while (index <= place) {
random += (Math.random() * 10).toInt() * divisor
divisor *= 10
index++
}
return if (random < 0) -random else random
}
}
data class UserConfig(
var username: String? = null,
var device: String? = null,
var project_id: String? = null,
var projectname: String? = null,
var projectcode: String? = null
)

View File

@@ -0,0 +1,331 @@
package com.pomelotea.hoperun.sign.error
import com.fasterxml.jackson.databind.ObjectMapper
import com.pomelotea.hoperun.sign.api.WebResult
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController
import org.springframework.boot.web.servlet.error.ErrorAttributes
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.http.HttpStatus
import org.springframework.validation.BindException
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.client.HttpServerErrorException
import org.springframework.web.servlet.NoHandlerFoundException
import org.springframework.web.servlet.view.RedirectView
import java.net.InetAddress
import java.net.UnknownHostException
import java.text.MessageFormat
import javax.servlet.ServletOutputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
/**
*
* @version 0.0.1
* @author jimlee
* date 2022-07-18 10:51
* 全局异常处理器
**/
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
@RequestMapping("/error")
open class ExtendExceptionHandler(errorAttributes: ErrorAttributes) : AbstractErrorController(errorAttributes) {
/**
* 参数断言异常
* @param request HttpServletRequest 请求
* @param response HttpServletResponse 响应
* @param ex Exception 服务异常
* @throws Exception 方法抛出异常
*/
@ExceptionHandler(IllegalArgumentException::class)
@Throws(Exception::class)
fun illegalArgumentException(request: HttpServletRequest, response: HttpServletResponse, ex: Exception) {
log.error(
MessageFormat.format(
"!!! request uri:{0} from {1} assert exception:{2}",
request.requestURI,
getIpAddr(request),
ex.message
), ex
)
out(
response,
"${if (ex.message != null && ex.message!!.length > 40) ex.message!!.substring(0, 40) else ex.message}",
false,
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ex.javaClass.name
)
}
/**
* 空指针异常
* @param request HttpServletRequest 请求
* @param response HttpServletResponse 响应
* @param ex Exception 服务异常
* @throws Exception 方法抛出异常
*/
@ExceptionHandler(NullPointerException::class)
@Throws(Exception::class)
fun nullPointerException(request: HttpServletRequest, response: HttpServletResponse, ex: Exception) {
log.error(
MessageFormat.format(
"!!! request uri:{0} from {1} null pointor exception:{2}",
request.requestURI,
getIpAddr(request),
ex.message
), ex
)
out(
response,
"服务器开小差了:${if (ex.message != null && ex.message!!.length > 40) ex.message!!.substring(0, 40) else ex.message}",
false,
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ex.javaClass.name
)
}
/**
* 其他异常
* @param request HttpServletRequest 请求
* @param response HttpServletResponse 响应
* @param ex Exception 服务异常
* @throws Exception 方法抛出异常
*/
@ExceptionHandler(Exception::class)
@Throws(Exception::class)
fun unknownException(request: HttpServletRequest, response: HttpServletResponse, ex: Exception) {
log.error(
MessageFormat.format(
"!!! request uri:{0} from {1} unknown exception:{2}",
request.requestURI,
getIpAddr(request),
ex.message
), ex
)
out(
response,
"服务器错误:${if (ex.message != null && ex.message!!.length > 40) ex.message!!.substring(0, 40) else ex.message}",
false,
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ex.javaClass.name
)
}
/**
* 500状态异常
* @param request HttpServletRequest 请求
* @param response HttpServletResponse 响应
* @param ex Exception 服务异常
* @throws Exception 方法抛出异常
*/
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(HttpServerErrorException.InternalServerError::class)
@Throws(Exception::class)
fun serverError(request: HttpServletRequest, response: HttpServletResponse, ex: Exception) {
log.error(
MessageFormat.format(
"!!! request uri:{0} from {1} server error[500] exception:{2}",
request.requestURI,
getIpAddr(request),
ex.message
), ex
)
out(
response,
"服务器内部错误:${if (ex.message != null && ex.message!!.length > 40) ex.message!!.substring(0, 40) else ex.message}",
false,
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ex.javaClass.name
)
}
/**
* 404状态异常
* @param request HttpServletRequest 请求
* @param response HttpServletResponse 响应
* @param ex Exception 服务异常
* @throws Exception 方法抛出异常
*/
@ResponseStatus(code = HttpStatus.NOT_FOUND)
@ExceptionHandler(NoHandlerFoundException::class)
@Throws(Exception::class)
fun notFound(request: HttpServletRequest, response: HttpServletResponse, ex: Exception) {
log.error(
MessageFormat.format(
"!!! request uri:{0} from {1} not found mapping url exception:{2}",
request.requestURI,
getIpAddr(request),
ex.message
), ex
)
out(
response,
"404 Not Fount:${if (ex.message != null && ex.message!!.length > 40) ex.message!!.substring(0, 40) else ex.message}",
false,
HttpStatus.NOT_FOUND.value(),
ex.javaClass.name
)
}
/**
* 请求校验缺少必要参数
* @param request HttpServletRequest 请求
* @param response HttpServletResponse 响应
* @param ex Exception 服务异常
* @throws Exception 方法抛出异常
*/
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException::class)
@Throws(Exception::class)
fun methodArgumentNotValidException(request: HttpServletRequest, response: HttpServletResponse, ex: MethodArgumentNotValidException) {
val msg = StringBuilder()
ex.bindingResult.fieldErrors.forEach {
msg.append(
"[${it.field}]:${it.defaultMessage}"
)
}
out(
response,
msg.toString(),
false,
HttpStatus.NOT_FOUND.value(),
ex.javaClass.name
)
}
/**
* 参数绑定请求实体错误
* @param request HttpServletRequest 请求
* @param response HttpServletResponse 响应
* @param ex Exception 服务异常
* @throws Exception 方法抛出异常
*/
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException::class)
@Throws(Exception::class)
fun bindException(request: HttpServletRequest, response: HttpServletResponse, ex: BindException) {
val msg = StringBuilder()
ex.bindingResult.fieldErrors.forEach {
msg.append(
"[${it.field}]:${it.defaultMessage}"
)
}
out(
response,
msg.toString(),
false,
HttpStatus.NOT_FOUND.value(),
ex.javaClass.name
)
}
@RequestMapping
@ResponseBody
@Throws(Exception::class)
fun handleErrors(request: HttpServletRequest, response: HttpServletResponse, e: Exception) {
val status = getStatus(request)
if (status === HttpStatus.NOT_FOUND) {
notFound(request, response, e)
} else if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
serverError(request, response, e)
} else unknownException(request, response, e)
}
@GetMapping
@ResponseBody
@Throws(Exception::class)
fun defaultGetError(request: HttpServletRequest, response: HttpServletResponse, e: Exception) {
handleErrors(request, response, e)
}
@ResponseStatus(code = HttpStatus.NOT_FOUND)
@RequestMapping(produces = ["text/html"])
@Throws(Exception::class)
fun handleHtml(request: HttpServletRequest, response: HttpServletResponse): RedirectView {
return RedirectView("/404")
}
companion object {
private val log: Logger = LoggerFactory.getLogger(ExtendExceptionHandler::class.java)
/**
* 全局异常错误输出
* @param response 响应对象
* @param msg 消息
* @param success 状态
* @param code 响应码
* @param data 响应实体
*/
@JvmStatic
fun out(response: HttpServletResponse, msg: String?, success: Boolean, code: Int?, data: Any? = null) {
val out: ServletOutputStream
try {
out = response.outputStream
response.reset()
response.characterEncoding = "UTF-8"
response.contentType = "application/json;charset=utf-8"
response.status = HttpStatus.OK.value()
out.write(ObjectMapper().writeValueAsBytes(WebResult.getWebResult(success, data, msg, code)))
} catch (e: Exception) {
log.error(e.toString() + "响应错误")
}
}
/**
* 获取请求IP地址
* @param request 请求
* @return String? ip地址
*/
fun getIpAddr(request: HttpServletRequest): String? {
var ipAddress: String?
return try {
ipAddress = request.getHeader("x-forwarded-for")
if (ipAddress == null || ipAddress.length == 0 || "unknown".equals(ipAddress, ignoreCase = true)) {
ipAddress = request.getHeader("Proxy-Client-IP")
}
if (ipAddress == null || ipAddress.length == 0 || "unknown".equals(ipAddress, ignoreCase = true)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP")
}
if (ipAddress == null || ipAddress.length == 0 || "unknown".equals(ipAddress, ignoreCase = true)) {
ipAddress = request.remoteAddr
// 本机访问, 获取网卡地址
if (ipAddress == "127.0.0.1" || ipAddress == "0:0:0:0:0:0:0:1") {
try {
ipAddress = InetAddress.getLocalHost().hostAddress
if (ipAddress != null) {
ipAddress += "(local)"
}
} catch (e: UnknownHostException) {
log.error("获取本机网卡IP失败", e)
}
}
}
if (ipAddress != null) {
if (ipAddress.contains(",")) {
ipAddress.split(",".toRegex()).toTypedArray()[0]
} else {
ipAddress
}
} else {
log.error("获取请求IP为空")
null
}
} catch (e: Exception) {
log.error("获取用户IP失败", e)
null
}
}
}
}

View File

@@ -0,0 +1,10 @@
server:
port: 8080
hoperun:
address: "浙江省杭州市西湖区转塘街道凌家桥路飞天园区120"
longitueHead: "120.085"
latitudeHead: "30.138"
longitueShort: "120.0845715522375"
latitudeShort: "30.140219809955912"
qingUa: "Qing/0.9.113"

View File

@@ -0,0 +1,34 @@
server:
port: 8080
error:
path: /error
logging:
level:
root: debug
file:
path: ./logs
logback:
rollingpolicy:
max-file-size: 50MB
max-history: 10
spring:
application:
name: 打卡平台
profiles:
active: dev
hoperun:
address: "浙江省杭州市西湖区转塘街道凌家桥路飞天园区120"
longitueHead: "120.085"
latitudeHead: "30.138"
longitueShort: "120.0845715522375"
latitudeShort: "30.140219809955912"
qingUa: "Qing/0.9.113"
userConfigMap:
"16638":
"project_id": "U2103S000112"
"projectcode": "U2103S000112"
"projectname": "JRKF-浙江网商-技术服务外包"
"device": "Android 12;Redmi;M2007J3SC;deviceId:OAIDe7fa6084205e9a22d8f6f71bc91893ff;deviceName:Android"

View File

@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 在spring boot应用中应该使用 logback-spring.xml 而不是logback.xml -->
<configuration>
<!-- 日志根目录 -->
<springProperty scope="context" name="logging.path" source="logging.file.path"/>
<!-- 日志级别 -->
<springProperty scope="context" name="logging.level" source="logging.level.root"/>
<!-- 单日志文件大小 -->
<springProperty scope="context" name="logging.size" source="logging.logback.rollingpolicy.max-file-size"/>
<!-- 历史记录数量 -->
<springProperty scope="context" name="logging.maxHistory" source="logging.logback.rollingpolicy.max-history"/>
<!-- 控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} [%line] - %msg%n</Pattern>
</encoder>
</appender>
<!-- 错误日志文件 -->
<appender name="ERROR-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${logging.path}/common-error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<maxFileSize>${logging.size}</maxFileSize>
<FileNamePattern>${logging.path}/common-error.log.%d{yyyy-MM-dd}.%i</FileNamePattern>
<MaxHistory>${logging.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} [%line] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 默认系统日志文件 -->
<appender name="ROOT-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>${logging.level}</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${logging.path}/common-default.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<maxFileSize>${logging.size}</maxFileSize>
<FileNamePattern>${logging.path}/common-default.log.%d{yyyy-MM-dd}.%i</FileNamePattern>
<MaxHistory>${logging.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} [%line] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- REST接口日志文件 -->
<appender name="REST-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>${logging.level}</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${logging.path}/rest.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<maxFileSize>${logging.size}</maxFileSize>
<FileNamePattern>${logging.path}/rest.log.%d{yyyy-MM-dd}.%i</FileNamePattern>
<MaxHistory>${logging.maxHistory}</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} [%line] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!--==================async appender======================= -->
<appender name="ASYNC-REST-APPENDER" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>256</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>true</neverBlock>
<appender-ref ref="REST-APPENDER"/>
</appender>
<!--==================logger======================= -->
<!-- boot logger -->
<logger name="APPLICATION-STARTER" level="${logging.level}" additivity="false">
<appender-ref ref="ERROR-APPENDER"/>
<appender-ref ref="ROOT-APPENDER"/>
</logger>
<!-- application logger -->
<logger name="com.pomelotea.hoperun.sign" level="${logging.level}" additivity="false">
<appender-ref ref="ROOT-APPENDER"/>
<appender-ref ref="ERROR-APPENDER"/>
</logger>
<root level="${logging.level}">
<appender-ref ref="ROOT-APPENDER"/>
<appender-ref ref="ERROR-APPENDER"/>
</root>
<!-- 本地调试日志输出到控制台 -->
<springProfile name="dev">
<!-- boot logger -->
<logger name="APPLICATION-STARTER" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ERROR-APPENDER"/>
<appender-ref ref="ROOT-APPENDER"/>
</logger>
<logger name="com.pomelotea.hoperun.sign" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ROOT-APPENDER"/>
<appender-ref ref="ERROR-APPENDER"/>
</logger>
<root level="debug">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ROOT-APPENDER"/>
<appender-ref ref="ERROR-APPENDER"/>
</root>
</springProfile>
</configuration>

View File

@@ -0,0 +1,12 @@
function _typeof(f){"@babel/helpers - typeof";_typeof="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(g){return typeof g}:function(g){return g&&"function"===typeof Symbol&&g.constructor===Symbol&&g!==Symbol.prototype?"symbol":typeof g};return _typeof(f)}
!function(f,g){"object"===("undefined"===typeof exports?"undefined":_typeof(exports))&&"undefined"!==typeof module?module.exports=g():"function"===typeof define&&define.amd?define(g):(f=f||self,f.cocoMessage=g())}(void 0,function(){function f(a,b){var c=document.createElement("div");for(e in a){var d=a[e];if("className"==e){var e="class";c.setAttribute(e,d)}else"_"==e[0]&&c.addEventListener(e.slice(1),d)}if("string"==typeof b)c.innerHTML=b;else if("object"==_typeof(b)&&b.tagName)c.appendChild(b);
else if(b)for(a=0;a<b.length;a++)c.appendChild(b[a]);return c}function g(a,b){["a","webkitA"].forEach(function(c){a.addEventListener(c+"nimationEnd",function(){b()})})}function t(a,b){for(var c in b)a.style[c]=b[c];""===a.getAttribute("style")&&a.removeAttribute("style")}function v(a,b){var c=a.className||"";-1<c.indexOf(b)||(c=c.split(/\s+/),c.push(b),a.className=c.join(" "))}function n(a,b){var c={},d;for(d in u)c[d]=u[d];for(d=0;d<a.length;d++){var e=a[d];void 0!==e&&("string"==typeof e||"object"==
_typeof(e)?c.msg=e:"boolean"==typeof e?c.showClose=e:"function"==typeof e?c.onClose=e:"number"==typeof e&&(c.duration=e))}c.type=b;return w(c)}function w(a){var b=a.type,c=a.duration,d=a.msg,e=a.showClose,p=a.onClose,q=0===c;"loading"==b&&(d=""===d?"\u6b63\u5728\u52a0\u8f7d\uff0c\u8bf7\u7a0d\u540e":d,q=e,c=0);a=f({className:"coco-msg-icon"},{info:'\n <svg t="1609810636603" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3250"><path d="M469.333333 341.333333h85.333334v469.333334H469.333333z" fill="#ffffff" p-id="3251"></path><path d="M469.333333 213.333333h85.333334v85.333334H469.333333z" fill="#ffffff" p-id="3252"></path><path d="M384 341.333333h170.666667v85.333334H384z" fill="#ffffff" p-id="3253"></path><path d="M384 725.333333h256v85.333334H384z" fill="#ffffff" p-id="3254"></path></svg>\n ',
success:'\n <svg t="1609781242911" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1807"><path d="M455.42 731.04c-8.85 0-17.75-3.05-24.99-9.27L235.14 553.91c-16.06-13.81-17.89-38.03-4.09-54.09 13.81-16.06 38.03-17.89 54.09-4.09l195.29 167.86c16.06 13.81 17.89 38.03 4.09 54.09-7.58 8.83-18.31 13.36-29.1 13.36z" p-id="1808" fill="#ffffff"></path><path d="M469.89 731.04c-8.51 0-17.07-2.82-24.18-8.6-16.43-13.37-18.92-37.53-5.55-53.96L734.1 307.11c13.37-16.44 37.53-18.92 53.96-5.55 16.43 13.37 18.92 37.53 5.55 53.96L499.67 716.89c-7.58 9.31-18.64 14.15-29.78 14.15z" p-id="1809" fill="#ffffff"></path></svg>\n ',
warning:'\n <svg t="1609776406944" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18912"><path d="M468.114286 621.714286c7.314286 21.942857 21.942857 36.571429 43.885714 36.571428s36.571429-14.628571 43.885714-36.571428L585.142857 219.428571c0-43.885714-36.571429-73.142857-73.142857-73.142857-43.885714 0-73.142857 36.571429-73.142857 80.457143l29.257143 394.971429zM512 731.428571c-43.885714 0-73.142857 29.257143-73.142857 73.142858s29.257143 73.142857 73.142857 73.142857 73.142857-29.257143 73.142857-73.142857-29.257143-73.142857-73.142857-73.142858z" p-id="18913" fill="#ffffff"></path></svg>\n ',
error:'\n <svg t="1609810716933" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5514"><path d="M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z" p-id="5515" fill="#ffffff"></path></svg>\n ',loading:'\n <div class="coco-msg_loading">\n <svg class="coco-msg-circular" viewBox="25 25 50 50">\n <circle class="coco-msg-path" cx="50" cy="50" r="20" fill="none" stroke-width="4" stroke-miterlimit="10"/>\n </svg>\n </div>\n '}[b]);
d=f({className:"coco-msg-content"},d);e={className:"coco-msg-wait "+(q?"coco-msg-pointer":""),_click:function(){q&&l(k,p)}};var k=f({className:"coco-msg-wrapper"},[f({className:"coco-msg coco-msg-fade-in "+b},[a,d,f(e,q?'\n <svg class="coco-msg-close" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5514"><path d="M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z" p-id="5515"></path></svg>\n ':'<svg class="coco-msg-progress" viewBox="0 0 33.83098862 33.83098862" xmlns="http://www.w3.org/2000/svg">\n <circle class="coco-msg__background" cx="16.9" cy="16.9" r="15.9"></circle>\n <circle class="coco-msg__circle" stroke-dasharray="100,100" cx="16.9" cy="16.9" r="15.9"></circle>\n </svg>\n ')])]);
if(d=k.querySelector(".coco-msg__circle"))t(d,{animation:"coco-msg_"+b+" "+c+"ms linear"}),"onanimationend"in window?g(d,function(){l(k,p)}):setTimeout(function(){l(k,p)},c);"loading"==b&&0!==c&&setTimeout(function(){l(k,p)},c);h.children.length||document.body.appendChild(h);h.appendChild(k);t(k,{height:k.offsetHeight+"px"});setTimeout(function(){var r=k.children[0],m=r.className||"";if(-1<m.indexOf("coco-msg-fade-in")){m=m.split(/\s+/);var x=m.indexOf("coco-msg-fade-in");m.splice(x,1);r.className=
m.join(" ")}""===r.className&&r.removeAttribute("class")},300);if("loading"==b)return function(){l(k,p)}}function l(a,b){a&&(t(a,{padding:0,height:0}),v(a.children[0],"coco-msg-fade-out"),b&&b(),setTimeout(function(){if(a){for(var c=!1,d=0;d<h.children.length;d++)h.children[d]===a&&(c=!0);c&&a&&a.parentNode.removeChild(a);a=null;h.children.length||c&&h&&h.parentNode.removeChild(h)}},300))}var h=f({className:"coco-msg-stage"}),u={msg:"",duration:2E3,showClose:!1};window.addEventListener("DOMContentLoaded",
function(){var a=document;if(a&&a.head){var b=a.head;a=a.createElement("style");a.innerHTML="\n\n[class|=coco],[class|=coco]::after,[class|=coco]::before{box-sizing:border-box;outline:0}.coco-msg-progress{width:13px;height:13px}.coco-msg__circle{stroke-width:2;stroke-linecap:square;fill:none;transform:rotate(-90deg);transform-origin:center}.coco-msg-stage:hover .coco-msg__circle{-webkit-animation-play-state:paused!important;animation-play-state:paused!important}.coco-msg__background{stroke-width:2;fill:none}.coco-msg-stage{position:fixed;top:20px;left:50%;width:auto;transform:translate(-50%,0);z-index:3000}.coco-msg-wrapper{position:relative;left:50%;transform:translate(-50%,0);transform:translate3d(-50%,0,0);transition:height .3s ease,padding .3s ease;padding:6px 0;will-change:transform,opacity}.coco-msg{padding:15px 21px;border-radius:3px;position:relative;left:50%;transform:translate(-50%,0);transform:translate3d(-50%,0,0);display:flex;align-items:center}.coco-msg-content,.coco-msg-icon,.coco-msg-wait{display:inline-block}.coco-msg-icon{position:relative;width:13px;height:13px;border-radius:100%;display:flex;justify-content:center;align-items:center}.coco-msg-icon svg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:11px;height:11px}.coco-msg-wait{width:20px;height:20px;position:relative;fill:#4eb127}.coco-msg-wait svg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.coco-msg-close{width:14px;height:14px}.coco-msg-content{margin:0 10px;min-width:240px;text-align:left;font-size:14px;font-weight:500;font-family:-apple-system,Microsoft Yahei,sans-serif;text-shadow:0 0 1px rgba(0,0,0,.01)}.coco-msg.info{color:#0fafad;background-color:#e7fdfc;box-shadow:0 0 2px 0 rgba(0,1,1,.01),0 0 0 1px #c0faf9}.coco-msg.info .coco-msg-icon{background-color:#0fafad}.coco-msg.success{color:#4ebb23;background-color:#f3ffe8;box-shadow:0 0 2px 0 rgba(0,1,0,.01),0 0 0 1px #d9f8bb}.coco-msg.success .coco-msg-icon{background-color:#4ebb23}.coco-msg.warning{color:#f1b306;background-color:#fff9eb;box-shadow:0 0 2px 0 rgba(1,1,0,.01),0 0 0 1px #fcf2cd}.coco-msg.warning .coco-msg-icon{background-color:#f1b306}.coco-msg.error{color:#f34b51;background-color:#fff7f7;box-shadow:0 0 2px 0 rgba(1,0,0,.01),0 0 0 1px #ffe3e3}.coco-msg.error .coco-msg-icon{background-color:#f34b51}.coco-msg.loading{color:#0fafad;background-color:#e7fdfc;box-shadow:0 0 2px 0 rgba(0,1,1,.01),0 0 0 1px #c2faf9}.coco-msg_loading{flex-shrink:0;width:20px;height:20px;position:relative}.coco-msg-circular{-webkit-animation:coco-msg-rotate 2s linear infinite both;animation:coco-msg-rotate 2s linear infinite both;transform-origin:center center;height:18px!important;width:18px!important}.coco-msg-path{stroke-dasharray:1,200;stroke-dashoffset:0;stroke:#0fafad;-webkit-animation:coco-msg-dash 1.5s ease-in-out infinite;animation:coco-msg-dash 1.5s ease-in-out infinite;stroke-linecap:round}@-webkit-keyframes coco-msg-rotate{100%{transform:translate(-50%,-50%) rotate(360deg)}}@keyframes coco-msg-rotate{100%{transform:translate(-50%,-50%) rotate(360deg)}}@-webkit-keyframes coco-msg-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:89,200;stroke-dashoffset:-35px}100%{stroke-dasharray:89,200;stroke-dashoffset:-124px}}@keyframes coco-msg-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:89,200;stroke-dashoffset:-35px}100%{stroke-dasharray:89,200;stroke-dashoffset:-124px}}.coco-msg.info .coco-msg-wait{fill:#0fafad}.coco-msg.success .coco-msg-wait{fill:#4ebb23}.coco-msg.warning .coco-msg-wait{fill:#f1b306}.coco-msg.error .coco-msg-wait{fill:#f34b51}.coco-msg.loading .coco-msg-wait{fill:#0fafad}.coco-msg-pointer{cursor:pointer}@-webkit-keyframes coco-msg_info{0%{stroke:#0fafad}to{stroke:#0fafad;stroke-dasharray:0 100}}@keyframes coco-msg_info{0%{stroke:#0fafad}to{stroke:#0fafad;stroke-dasharray:0 100}}@-webkit-keyframes coco-msg_success{0%{stroke:#4eb127}to{stroke:#4eb127;stroke-dasharray:0 100}}@keyframes coco-msg_success{0%{stroke:#4eb127}to{stroke:#4eb127;stroke-dasharray:0 100}}@-webkit-keyframes coco-msg_warning{0%{stroke:#fcbc0b}to{stroke:#fcbc0b;stroke-dasharray:0 100}}@keyframes coco-msg_warning{0%{stroke:#fcbc0b}to{stroke:#fcbc0b;stroke-dasharray:0 100}}@-webkit-keyframes coco-msg_error{0%{stroke:#eb262d}to{stroke:#eb262d;stroke-dasharray:0 100}}@keyframes coco-msg_error{0%{stroke:#eb262d}to{stroke:#eb262d;stroke-dasharray:0 100}}.coco-msg-fade-in{-webkit-animation:coco-msg-fade .2s ease-out both;animation:coco-msg-fade .2s ease-out both}.coco-msg-fade-out{animation:coco-msg-fade .3s linear reverse both}@-webkit-keyframes coco-msg-fade{0%{opacity:0;transform:translate(-50%,0);transform:translate3d(-50%,-80%,0)}to{opacity:1;transform:translate(-50%,0);transform:translate3d(-50%,0,0)}}@keyframes coco-msg-fade{0%{opacity:0;transform:translate(-50%,0);transform:translate3d(-50%,-80%,0)}to{opacity:1;transform:translate(-50%,0);transform:translate3d(-50%,0,0)}}\n ";
b.children.length?b.insertBefore(a,b.children[0]):b.appendChild(a)}});return{info:function(){n(arguments,"info")},success:function(){n(arguments,"success")},warning:function(){n(arguments,"warning")},error:function(){n(arguments,"error")},loading:function(){return n(arguments,"loading")},destroyAll:function(){for(var a=0;a<h.children.length;a++)l(h.children[a])},config:function(a){for(var b in a)Object.hasOwnProperty.call(a,b)&&void 0!==a[b]&&(u[b]=a[b])}}});

View File

@@ -0,0 +1,15 @@
x<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="ico_checkon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 42 42" style="enable-background:new 0 0 42 42;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#00A4FF;}
.st1{fill:#FFFFFF;}
</style>
<g>
<g>
<polygon class="st0" points="42,42 42,0.5 0.5,42 "/>
</g>
</g>
<path class="st1" d="M19.4,26.9l8.5,8.5L25,38.2l-8.5-8.5L19.4,26.9z M36.4,21.2l2.8,2.8L25.1,38.2l-2.8-2.8L36.4,21.2z"/>
</svg>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>打卡</title>
<link rel="stylesheet" href="./normalize.min.css">
<link rel="stylesheet" href="./style.css">
</head>
<body><!--partial:index.partial.html-->
<div class="job">
<div class="header">
<div class="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path xmlns="http://www.w3.org/2000/svg"
d="M512 503.5H381.7a48 48 0 01-45.3-32.1L265 268.1l-9-25.5 2.7-124.6L338.2 8.5l23.5 67.1L512 503.5z"
fill="#0473ff" data-original="#28b446"/>
<path xmlns="http://www.w3.org/2000/svg" fill="#0473ff" data-original="#219b38"
d="M361.7 75.6L265 268.1l-9-25.5 2.7-124.6L338.2 8.5z"/>
<path xmlns="http://www.w3.org/2000/svg"
d="M338.2 8.5l-82.2 234-80.4 228.9a48 48 0 01-45.3 32.1H0l173.8-495h164.4z" fill="#0473ff"
data-original="#518ef8"/>
</svg>
打卡
</div>
<div class="user-settings">
<div class="dark-light">
<svg viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path>
</svg>
</div>
<img class="user-profile" id="user-head" src="img/tx.png" alt="">
<div id="user-div">
<div id="username">未登陆</div>
<span id="login-span"><div class="alert-inline"><div><input class="login-input" id="employee-number"
type="text" placeholder="输入工号"></div><div><button
class="random-buttons" id="login">登陆</button></div></div></span>
<div id="logout-div" style="display: none">
<button class="random-buttons" id="logout">登出</button>
</div>
</div>
</div>
</div>
<div class="dialog" style="display: none">
<div class="alert">
<div class="job-card-title" id="sign-date"><span style="font-size: 24px"></span></div>
<div class="alert-inline">
<div><input id="sign-begin-time" type="text" placeholder="输入上班时间"></div>
<div>
<button class="random-buttons" id="sign-begin-random">随机</button>
</div>
</div>
<div class="alert-inline">
<div><input id="sign-end-time" type="text" placeholder="输入下班时间"></div>
<div>
<button class="random-buttons" id="sign-end-random">随机</button>
</div>
</div>
<button class="search-buttons card-buttons begin-buttons" id="save-sign">保存</button>
</div>
</div>
<div class="login_dialog" style="display: none">
<div class="alert">
<div class="job-card-title" ><span style="font-size: 24px">打卡信息</span></div>
<div class="alert-inline">
<div>
<label for="_user_input">用 户 名 : </label>
<input id="_user_input" readonly="readonly" type="text">
</div>
</div>
<div class="alert-inline">
<div>
<label for="_project_id_input">项 目 I D: </label>
<input id="_project_id_input" readonly="readonly" type="text">
</div>
</div>
<div class="alert-inline">
<div>
<label for="_project_name_input">项目名称: </label>
<input id="_project_name_input" readonly="readonly" type="text">
</div>
</div>
<div class="alert-inline">
<div>
<label for="_area_input">打卡地区: </label>
<input id="_area_input" readonly="readonly" type="text">
</div>
</div>
<div class="alert-inline">
<div>
<label for="_device_input">设备信息: </label>
<input id="_device_input" readonly="readonly" type="text">
</div>
</div>
</div>
</div>
<div class="wrapper">
<div class="main-container">
<div class="searched-jobs">
<div class="searched-bar">
<div class="searched-show">打卡记录</div>
<!--<button class="search-button">餐补<span class="canbu-span"></span></button>--></div>
<div class="job-cards"></div>
</div>
</div>
</div>
<script src="./coco-message.js"></script>
<script src="./jquery.min.js"></script>
<script src="./script.js"></script>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>打卡</title><link rel="stylesheet"href="./normalize.min.css"><link rel="stylesheet"href="./style.css"></head><body><!--partial:index.partial.html--><div class="job"><div class="header"><div class="logo"><svg xmlns="http://www.w3.org/2000/svg"viewBox="0 0 512 512"><path xmlns="http://www.w3.org/2000/svg"d="M512 503.5H381.7a48 48 0 01-45.3-32.1L265 268.1l-9-25.5 2.7-124.6L338.2 8.5l23.5 67.1L512 503.5z"fill="#0473ff"data-original="#28b446"/><path xmlns="http://www.w3.org/2000/svg"fill="#0473ff"data-original="#219b38"d="M361.7 75.6L265 268.1l-9-25.5 2.7-124.6L338.2 8.5z"/><path xmlns="http://www.w3.org/2000/svg"d="M338.2 8.5l-82.2 234-80.4 228.9a48 48 0 01-45.3 32.1H0l173.8-495h164.4z"fill="#0473ff"data-original="#518ef8"/></svg>打卡</div><div class="user-settings"><div class="dark-light"><svg viewBox="0 0 24 24"stroke="currentColor"stroke-width="1.5"fill="none"stroke-linecap="round"stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path></svg></div><img class="user-profile"id="user-head"src="img/tx.png"alt=""><div id="user-div"><div id="username">未登陆</div><span id="login-span"><div class="alert-inline"><div><input class="login-input"id="employee-number"type="text"placeholder="输入工号"></div><div><button class="random-buttons"id="login">登陆</button></div></div></span><div id="logout-div"style="display: none"><button class="random-buttons"id="logout">登出</button></div></div></div></div><div class="dialog"style="display: none"><div class="alert"><div class="job-card-title"id="sign-date"><span style="font-size: 24px"></span></div><div class="alert-inline"><div><input id="sign-begin-time"type="text"placeholder="输入上班时间"></div><div><button class="random-buttons"id="sign-begin-random">随机</button></div></div><div class="alert-inline"><div><input id="sign-end-time"type="text"placeholder="输入下班时间"></div><div><button class="random-buttons"id="sign-end-random">随机</button></div></div><button class="search-buttons card-buttons begin-buttons"id="save-sign">保存</button></div></div><div class="login_dialog"style="display: none"><div class="alert"><div class="job-card-title"><span style="font-size: 24px">打卡信息</span></div><div class="alert-inline"><div><label for="_user_input">用 户 名 :</label><input id="_user_input"readonly="readonly"type="text"></div></div><div class="alert-inline"><div><label for="_project_id_input">项 目 I D:</label><input id="_project_id_input"readonly="readonly"type="text"></div></div><div class="alert-inline"><div><label for="_project_name_input">项目名称:</label><input id="_project_name_input"readonly="readonly"type="text"></div></div><div class="alert-inline"><div><label for="_area_input">打卡地区:</label><input id="_area_input"readonly="readonly"type="text"></div></div><div class="alert-inline"><div><label for="_device_input">设备信息:</label><input id="_device_input"readonly="readonly"type="text"></div></div></div></div><div class="wrapper"><div class="main-container"><div class="searched-jobs"><div class="searched-bar"><div class="searched-show">打卡记录</div><!--<button class="search-button">餐补<span class="canbu-span"></span></button>--></div><div class="job-cards"></div></div></div></div><script src="./coco-message.js"></script><script src="./jquery.min.js"></script><script src="./script.js"></script></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,190 @@
button,hr,input {
overflow: visible
}
audio,canvas,progress,video {
display: inline-block
}
progress,sub,sup {
vertical-align: baseline
}
html {
font-family: sans-serif;
line-height: 1.15;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%
}
body {
margin: 0
}
menu,article,aside,details,footer,header,nav,section {
display: block
}
h1 {
font-size: 2em;
margin: .67em 0
}
figcaption,figure,main {
display: block
}
figure {
margin: 1em 40px
}
hr {
box-sizing: content-box;
height: 0
}
code,kbd,pre,samp {
font-family: monospace,monospace;
font-size: 1em
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active,a:hover {
outline-width: 0
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted
}
b,strong {
font-weight: bolder
}
dfn {
font-style: italic
}
mark {
background-color: #ff0;
color: #000
}
small {
font-size: 80%
}
sub,sup {
font-size: 75%;
line-height: 0;
position: relative
}
sub {
bottom: -.25em
}
sup {
top: -.5em
}
audio:not([controls]) {
display: none;
height: 0
}
img {
border-style: none
}
svg:not(:root) {
overflow: hidden
}
button,input,optgroup,select,textarea {
font-family: sans-serif;
font-size: 100%;
line-height: 1.15;
margin: 0
}
button,input {
}
button,select {
text-transform: none
}
[type=submit], [type=reset],button,html [type=button] {
-webkit-appearance: button
}
[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner {
border-style: none;
padding: 0
}
[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring {
outline: ButtonText dotted 1px
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal
}
progress {
}
textarea {
overflow: auto
}
[type=checkbox],[type=radio] {
box-sizing: border-box;
padding: 0
}
[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button {
height: auto
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px
}
[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit
}
summary {
display: list-item
}
[hidden],template {
display: none
}
/*# sourceMappingURL=normalize.min.css.map */

View File

@@ -0,0 +1,310 @@
const toggleButton = document.querySelector(".dark-light");
toggleButton.addEventListener("click", () => {
document.body.classList.toggle("dark-mode");
});
if (window.localStorage.getItem("employeeNo") != null && window.localStorage.getItem("employeeNo") !== '') {
loginAndLoadRecord()
} else {
cocoMessage.info(2000, "请先登录!");
}
document.querySelector('#logout').addEventListener("click", () => {
window.localStorage.removeItem("employeeNo");
window.localStorage.removeItem("username");
$('.job-cards').text("");
$('#employee-number').val("");
$('#username').text("未登陆");
$("#logout-div").hide();
$("#login-span").show();
})
let employeeNumberInput = document.querySelector('#employee-number');
if ($('#login-span').css('display') !== 'none') {
employeeNumberInput.focus()
}
employeeNumberInput.addEventListener("keypress", (e) => {
if (e.keyCode === 13) {
window.localStorage.setItem("employeeNo", employeeNumberInput.value);
loginAndLoadRecord();
}
})
document.querySelector('#login').addEventListener("click", () => {
window.localStorage.setItem("employeeNo", employeeNumberInput.value);
loginAndLoadRecord();
})
function loginAndLoadRecord() {
$.ajax({
method: "get",
url: "/api/daka/username/" + window.localStorage.getItem("employeeNo"),
success: function (result) {
if (result.success) {
$("#login-span").css("display", "none");
$('#username').text(result.data.username);
window.localStorage.setItem("username", result.data.username);
$("#logout-div").css("display", "block");
// 登录信息
$('#_user_input').val(result.data.username)
$('#_project_id_input').val(result.data.project_id)
$('#_project_name_input').val(result.data.projectname)
$('#_area_input').val(result.data.area)
$('#_device_input').val(result.data.device)
let loginDialog = $('.login_dialog')
loginDialog.show();
setTimeout(
() => {
loginDialog.hide();
}, 3000
)
$('#user-head').off('click').on("click", () => {
if (loginDialog.css("display") === 'block') {
$('.login_dialog').hide()
} else {
$('.login_dialog').show()
}
})
loadDakaList();
} else {
window.localStorage.setItem("employeeNo", '');
cocoMessage.error("登陆失败!" + result.message, 3000);
}
},
error: function (e) {
cocoMessage.error("请求失败!", 3000);
}
})
}
let dateBeginTimeMap = {};
let dateEndTimeMap = {};
function loadDakaList() {
let closeLoading = cocoMessage.loading(true);
$.ajax({
method: "get",
url: "/api/daka/last/" + window.localStorage.getItem("employeeNo"),
success: function (result) {
if (result.success) {
let data = result.data;
let date = new Date();
let nowDate = date.getFullYear() + '-' + ((date.getMonth() + 1) < 10 ? '0' + date.getMonth() + 1 : date.getMonth() + 1) + '-' + (date.getDate() < 10 ? '0' + date.getDate() : date.getDate());
let index = 0;
for (var signlog of data) {
dateBeginTimeMap[signlog.yearmonth] = (signlog.begin_time == null ? "未打卡" : signlog.begin_time.substr(11, 5));
dateEndTimeMap[signlog.yearmonth] = (signlog.end_time == null ? "未打卡" : signlog.end_time.substr(11, 5));
index++;
$('.job-cards').append('<div class="job-card">' +
'<div class="job-card-title">' + (nowDate === signlog.yearmonth ? "今天" : signlog.yearmonth) + '</div>' +
'<div class="job-card-subtitle">' +
signlog.area_id +
'</div>' +
'<div class="job-detail-buttons">' +
'<button class="search-buttons detail-button">' + (signlog.begin_time == null ? "未打卡" : signlog.begin_time.substr(11, 5)) + '</button>' +
'<button class="search-buttons detail-button">' + (signlog.end_time == null ? "未打卡" : signlog.end_time.substr(11, 5)) + '</button>' +
(signlog.end_time != null && signlog.end_time.substr(11, 5) > "20:30" ? '<button class="search-buttons detail-button">餐补</button>' : '') +
'</div>' +
(index <= 3 ? '<div class="job-card-buttons">' +
'<button class="search-buttons card-buttons daka-buttons"' +
'">打卡</button>' +
'<button class="search-buttons card-buttons auto-daka-buttons">自动补卡</button>' +
'</div>' : '') +
'</div>'
);
}
bindDakaClick();
bindAutoDakaClick();
bindRandomBeginTime();
bindRandomEndTime();
bindSaveDakaTime();
} else {
cocoMessage.error("加载打卡记录失败!" + result.message, 3000);
}
closeLoading()
},
error: function (e) {
cocoMessage.error("请求失败!", 3000);
closeLoading()
}
});
}
function bindDakaClick() {
document.querySelectorAll(".daka-buttons").forEach((dakaButton) => {
$(dakaButton).off('click').on('click', function (e) {
let dialogDom = $('.dialog');
dialogDom.css("left", e.clientX)
dialogDom.css("top", e.clientY)
let titleDate = e.target.parentElement.parentElement.children[0];
let times = e.target.parentElement.parentElement.children[2].children;
let beginTime = times[0].outerText
let date = new Date()
let nowTime = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':' + (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes())
if (beginTime === '未打卡' && nowTime < '09:30') {
beginTime = nowTime
}
let endTime = times[1].outerText
if (endTime === '未打卡' && nowTime > '18:00') {
endTime = nowTime
}
$('#sign-date > span').text(titleDate.outerText);
$('#sign-begin-time').val(beginTime);
$('#sign-end-time').val(endTime);
dialogDom.show();
})
})
}
function bindAutoDakaClick() {
document.querySelectorAll(".auto-daka-buttons").forEach((autoDakaButton) => {
$(autoDakaButton).off('click').on('click', function (e) {
let titleDate = e.target.parentElement.parentElement.children[0].outerText;
let times = e.target.parentElement.parentElement.children[2].children;
let beginTime = times[0].outerText;
let endTime = times[1].outerText;
if (beginTime > "09:30") {
beginTime = randomBeginTime();
}
if (endTime === "未打卡" || endTime < '20:30') {
endTime = randomEndTime();
}
saveDakaTime(titleDate, beginTime, endTime);
})
})
}
function bindRandomBeginTime() {
$('#sign-begin-random').off('click').on("click", () => {
$('#sign-begin-time').val(randomBeginTime());
})
}
function bindRandomEndTime() {
$('#sign-end-random').off('click').on("click", () => {
$('#sign-end-time').val(randomEndTime());
})
}
function bindSaveDakaTime() {
$('#save-sign').off('click').on("click", () => {
let signDate = $('#sign-date > span').text();
let beginTime = $('#sign-begin-time').val();
let endTime = $('#sign-end-time').val();
saveDakaTime(signDate, beginTime, endTime);
})
}
function saveDakaTime(signDate, beginTime, endTime) {
let messageType = 0;
let beginTimeMessage = dateBeginTimeMap[signDate] !== beginTime;
let endTimeMessage = endTime !== '' && dateEndTimeMap[signDate] !== endTime;
if (beginTimeMessage && endTimeMessage) {
messageType = 1;
} else if (beginTimeMessage && !endTimeMessage) {
messageType = 2;
} else if (!beginTimeMessage && endTimeMessage) {
messageType = 3;
} else {
messageType = 4;
}
if (dateBeginTimeMap[signDate] !== beginTime) {
$.ajax({
method: "post",
url: "/api/daka/beginTime",
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify({
'employeeNo': window.localStorage.getItem("employeeNo"),
'date': signDate,
'time': beginTime
}),
success: function (result) {
if (!result.success) {
success = false;
cocoMessage.error("打上班卡失败!" + result.message, 3000);
} else {
if (messageType === 2) {
setTimeout(function () {
$('.dialog').hide();
$('.job-cards').html('');
loadDakaList();
}, 1000);
}
}
},
error: function (e) {
cocoMessage.error("请求失败!", 3000);
}
});
}
if (endTime !== '' && dateEndTimeMap[signDate] !== endTime) {
$.ajax({
method: "post",
url: "/api/daka/endTime",
contentType: "application/json; charset=utf-8",
dataType: 'json',
data: JSON.stringify({
'employeeNo': window.localStorage.getItem("employeeNo"),
'date': signDate,
'time': endTime
}),
success: function (result) {
if (!result.success) {
success = false;
cocoMessage.error("打下班卡失败!" + result.message, 3000);
} else {
if (messageType === 1 || messageType === 3) {
setTimeout(function () {
$('.dialog').hide();
$('.job-cards').html('');
loadDakaList();
}, 1000);
}
}
},
error: function (e) {
cocoMessage.error("请求失败!", 3000);
}
});
}
if (messageType === 4) {
cocoMessage.info(1000, "没有变更");
}
}
function randomBeginTime() {
let randomMinutes = Math.round(Math.random() * 30);
return "09:" + (randomMinutes < 10 ? "0" + randomMinutes : randomMinutes);
}
function randomEndTime() {
let hourArray = [20, 21, 22, 23, 20, 21, 22, 20, 20, 21];
let randomHour = hourArray[Math.round(Math.random() * 9)];
let randomMinute = 0;
if (randomHour === 20) {
randomMinute = Math.round(Math.random() * 30) + 29;
} else {
randomMinute = Math.round(Math.random() * 59);
if (randomMinute < 10) {
randomMinute = '0' + randomMinute;
}
}
return randomHour + ":" + randomMinute;
}
$(document).click(function(e) {
var $target = $(e.target);
//点击表情选择按钮和表情选择框以外的地方 隐藏表情选择框
if (!$target.is('.dialog *') && !$target.is('.daka-buttons')) {
$('.dialog').hide();
}
if (!$target.is('.login_dialog *') && !$target.is('#user-head')) {
$('.login_dialog').hide();
}
})

File diff suppressed because it is too large Load Diff