From 9a0d28e97105adc7979b2450b48ee2e912301493 Mon Sep 17 00:00:00 2001 From: liurunyu <lry9898@163.com> Date: 星期三, 24 四月 2024 16:38:35 +0800 Subject: [PATCH] Merge branch 'master' of http://8.140.179.55:20000/r/pms-SV --- /dev/null | 29 ----- pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/pojoBa/BaLog.java | 26 ++++- pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogAspect.java | 67 ++++++++---- pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogService.java | 27 +++++ pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/QueryVo.java | 4 pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/LogSv.java | 3 pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/LoginVo.java | 4 pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/SsoCtrl.java | 88 ++++++++++++++++- pms-parent/pms-global/src/main/resources/mapper/BaLogMapper.xml | 52 ++++++---- 9 files changed, 210 insertions(+), 90 deletions(-) diff --git a/pms-parent/pms-common/src/main/java/com/dy/common/util/ObjectToMapUtil.java b/pms-parent/pms-common/src/main/java/com/dy/common/util/ObjectToMapUtil.java deleted file mode 100644 index 8165e98..0000000 --- a/pms-parent/pms-common/src/main/java/com/dy/common/util/ObjectToMapUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.dy.common.util; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -public class ObjectToMapUtil { - - /** - * 灏嗗璞¤浆鎹负Map銆� - * - * @param obj 瑕佽浆鎹㈢殑瀵硅薄銆� - * @return 鍖呭惈瀵硅薄瀛楁鍜屽�肩殑Map銆� - */ - public static Map<String, Object> objectToMap(Object obj) { - if (obj == null) { - return null; - } - - Map<String, Object> map = new HashMap<>(); - Class<?> clazz = obj.getClass(); - Field[] fields = clazz.getDeclaredFields(); - - for (Field field : fields) { - try { - field.setAccessible(true); // 璁剧疆瀛楁鍙闂� - map.put(field.getName(), field.get(obj)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Error accessing field: " + field.getName(), e); - } - } - - return map; - } -} diff --git a/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogAspect.java b/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogAspect.java index 0902667..1086ddc 100644 --- a/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogAspect.java +++ b/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogAspect.java @@ -1,12 +1,14 @@ package com.dy.pmsGlobal.aop; -import com.dy.common.util.ObjectToMapUtil; +import com.alibaba.fastjson2.JSONObject; import com.dy.common.webFilter.UserTokenContext; import com.dy.common.webUtil.BaseResponse; import com.dy.common.webUtil.BaseResponseUtils; import com.dy.pmsGlobal.pojoBa.BaUser; +import com.fasterxml.jackson.databind.ObjectMapper; import com.mysql.cj.util.StringUtils; import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; @@ -23,8 +25,7 @@ import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.util.UriComponentsBuilder; -import java.util.Map; - +@Slf4j @Aspect @Component public class LogAspect { @@ -32,9 +33,9 @@ @Value("${pms.sso.curUserUrl}") public String SsoCurUserUrl ; - private LogSv logSv; + private LogService logSv; @Autowired - public void setLogSv(LogSv logSv){ + public void setLogSv(LogService logSv){ this.logSv = logSv; } private RestTemplate restTemplate; @@ -44,22 +45,24 @@ } @AfterReturning(pointcut = "@annotation(com.dy.pmsGlobal.aop.Log)", returning = "result") - public void logAfterReturning(JoinPoint joinPoint, Object result) { - // 鑾峰彇鏂规硶鐨勪腑鏂囨弿杩� - MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); - Log operationDescription = methodSignature.getMethod().getAnnotation(Log.class); - String operationName = operationDescription.value(); - //缁撴灉 - BaseResponse response = (BaseResponse)result; - response.isSuccess(); - // 鑾峰彇鐢ㄦ埛淇℃伅 - BaUser user = (BaUser)getCurUser(response); - Long operator = user!=null?user.id: 1l; - - // 鑾峰彇IP鍦板潃 - String ipAddress = getRemoteHost(); - // 璁板綍鏃ュ織 - logSv.save(operator, operationName,ipAddress); + public void logAfterReturning(JoinPoint joinPoint, BaseResponse result) { + try{ + // 鑾峰彇鐢ㄦ埛淇℃伅 + BaUser user = (BaUser)getCurUser(result); + if(user!=null && user.id !=null && !StringUtils.isNullOrEmpty(user.name)){ + Long operator = user.id; + // 鑾峰彇鏂规硶鐨勪腑鏂囨弿杩� + MethodSignature sign = (MethodSignature) joinPoint.getSignature(); + Log logDesc = sign.getMethod().getAnnotation(Log.class); + String operationName = logDesc.value(); + // 鑾峰彇IP鍦板潃 + String ip = getRemoteHost(); + // 璁板綍鏃ュ織 + logSv.save(operator, operationName,ip,result.getCode(),result.getMsg()); + } + }catch (Exception e){ + log.error("璁板綍鏃ュ織寮傚父:"+e.getMessage()); + } } /** @@ -71,8 +74,10 @@ if(!StringUtils.isNullOrEmpty(SsoCurUserUrl)){ String token = UserTokenContext.get(); if(StringUtils.isNullOrEmpty(token)){ - Map<String,Object> resMap = ObjectToMapUtil.objectToMap(response.getContent()); - token =resMap.containsKey("token")?resMap.get("token").toString():""; + JSONObject res = objectToJson(response.getContent()); + if(res!=null && res.containsKey("token")){ + token = res.get("token").toString(); + } } String url = UriComponentsBuilder.fromUriString(SsoCurUserUrl) @@ -114,4 +119,20 @@ } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; } + + /** + * 灏嗗璞¤浆鎹负JSONObject + * @param obj + * @return + */ + public static JSONObject objectToJson(Object obj) { + ObjectMapper mapper = new ObjectMapper(); + try { + JSONObject o = JSONObject.parseObject(mapper.writeValueAsString(obj)); + return o; + } catch (Exception e) { + log.error("瀵硅薄杞崲涓篔SONObject澶辫触:"+e.getMessage()); + return null; + } + } } diff --git a/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogService.java b/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogService.java new file mode 100644 index 0000000..5ca8a39 --- /dev/null +++ b/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogService.java @@ -0,0 +1,27 @@ +package com.dy.pmsGlobal.aop; + +import com.dy.pmsGlobal.daoBa.BaLogMapper; +import com.dy.pmsGlobal.pojoBa.BaLog; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; + +@Service +public class LogService { + + @Autowired + private BaLogMapper dao; + + public void save(long operator, String operation,String ip,String code,String msg) { + BaLog log = new BaLog(); + log.userId=operator; + log.content = operation; + log.dt = new Date(); + log.ip = ip; + log.code = code; + log.msg = msg; + dao.insert(log); + } + +} diff --git a/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogSv.java b/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogSv.java deleted file mode 100644 index f31954b..0000000 --- a/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/aop/LogSv.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.dy.pmsGlobal.aop; - -import com.dy.common.webUtil.QueryResultVo; -import com.dy.pmsGlobal.daoBa.BaLogMapper; -import com.dy.pmsGlobal.pojoBa.BaLog; -import org.apache.dubbo.common.utils.PojoUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.Date; -import java.util.List; -import java.util.Map; - -@Service -public class LogSv { - - @Autowired - private BaLogMapper dao; - - public void save(long operator, String operation,String resIp) { - BaLog log = new BaLog(); - log.setUserId(operator); - log.setContent(operation); - log.setCreateDt(new Date()); - log.setResIp(resIp); - dao.insert(log); - } - -} diff --git a/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/pojoBa/BaLog.java b/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/pojoBa/BaLog.java index dc0b617..4ab5877 100644 --- a/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/pojoBa/BaLog.java +++ b/pms-parent/pms-global/src/main/java/com/dy/pmsGlobal/pojoBa/BaLog.java @@ -1,5 +1,9 @@ package com.dy.pmsGlobal.pojoBa; +import com.alibaba.fastjson2.annotation.JSONField; +import com.alibaba.fastjson2.writer.ObjectWriterImplToString; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; @@ -12,25 +16,37 @@ @NoArgsConstructor @AllArgsConstructor public class BaLog { - private Long id; + + @JSONField(serializeUsing= ObjectWriterImplToString.class) + @TableId(value = "id", type = IdType.INPUT) + public Long id; /** * 鐢ㄦ埛ID */ - private Long userId; + public Long userId; /** * 鏃ュ織鍐呭 */ - private String content; + public String content; /** * 璇锋眰IP */ - private String resIp; + public String ip; + + /** + * 鍝嶅簲鐘舵�佺爜 + */ + public String code; + /** + * 鍝嶅簲淇℃伅 + */ + public String msg; /** * 鍒涘缓鏃堕棿 */ - private Date createDt; + public Date dt; } \ No newline at end of file diff --git a/pms-parent/pms-global/src/main/resources/mapper/BaLogMapper.xml b/pms-parent/pms-global/src/main/resources/mapper/BaLogMapper.xml index c7cd531..7a2bcf7 100644 --- a/pms-parent/pms-global/src/main/resources/mapper/BaLogMapper.xml +++ b/pms-parent/pms-global/src/main/resources/mapper/BaLogMapper.xml @@ -7,12 +7,14 @@ <id column="id" jdbcType="BIGINT" property="id" /> <result column="user_id" jdbcType="BIGINT" property="userId" /> <result column="content" jdbcType="VARCHAR" property="content" /> - <result column="res_ip" jdbcType="VARCHAR" property="resIp" /> - <result column="create_dt" jdbcType="TIMESTAMP" property="createDt" /> + <result column="ip" jdbcType="VARCHAR" property="ip" /> + <result column="code" jdbcType="VARCHAR" property="code" /> + <result column="msg" jdbcType="VARCHAR" property="msg" /> + <result column="dt" jdbcType="TIMESTAMP" property="dt" /> </resultMap> <sql id="Base_Column_List"> <!--@mbg.generated--> - id, user_id, content, create_dt,res_ip + id, user_id, content, dt,ip,code,msg </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap"> <!--@mbg.generated--> @@ -23,10 +25,10 @@ </select> <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.dy.pmsGlobal.pojoBa.BaLog" useGeneratedKeys="true"> <!--@mbg.generated--> - insert into ba_log (user_id, content, create_dt,res_ip + insert into ba_log (user_id, content, ip,code,msg ) - values (#{userId,jdbcType=BIGINT}, #{content,jdbcType=VARCHAR}, #{createDt,jdbcType=TIMESTAMP} - , #{resIp,jdbcType=VARCHAR} + values (#{userId,jdbcType=BIGINT}, #{content,jdbcType=VARCHAR}, #{ip,jdbcType=VARCHAR}, + #{code,jdbcType=VARCHAR},#{msg,jdbcType=VARCHAR} ) </insert> <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.dy.pmsGlobal.pojoBa.BaLog" useGeneratedKeys="true"> @@ -39,11 +41,14 @@ <if test="content != null"> content, </if> - <if test="createDt != null"> - create_dt, + <if test="code != null"> + code, </if> - <if test="resIp != null"> - res_ip, + <if test="msg != null"> + msg, + </if> + <if test="ip != null"> + ip, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> @@ -53,11 +58,14 @@ <if test="content != null"> #{content,jdbcType=VARCHAR}, </if> - <if test="createDt != null"> - #{createDt,jdbcType=TIMESTAMP}, + <if test="code != null"> + #{code,jdbcType=VARCHAR}, </if> - <if test="resIp != null"> - #{resIp,jdbcType=VARCHAR}, + <if test="msg != null"> + #{msg,jdbcType=VARCHAR}, + </if> + <if test="ip != null"> + #{ip,jdbcType=VARCHAR}, </if> </trim> </insert> @@ -75,11 +83,11 @@ <if test="content != null and content != '' "> content like concat('%', #{content}, '%') and </if> - <if test="createDt != null and createDt != '' "> - create_dt = #{createDt,jdbcType=TIMESTAMP} and + <if test="dt != null and dt != '' "> + dt = #{dt,jdbcType=TIMESTAMP} and </if> - <if test="resIp != null and resIp != '' "> - res_ip =#{resIp,jdbcType=VARCHAR} and + <if test="ip != null and ip != '' "> + ip =#{ip,jdbcType=VARCHAR} and </if> </trim> order by id desc @@ -100,11 +108,11 @@ <if test="content != null and content != '' "> content like concat('%', #{content}, '%') and </if> - <if test="createDt != null and createDt != '' "> - create_dt = #{createDt,jdbcType=TIMESTAMP} and + <if test="dt != null and dt != '' "> + dt = #{dt,jdbcType=TIMESTAMP} and </if> - <if test="resIp != null and resIp != '' "> - res_ip =#{resIp,jdbcType=VARCHAR} and + <if test="ip != null and ip != '' "> + ip =#{ip,jdbcType=VARCHAR} and </if> </trim> </select> diff --git a/pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/LogSv.java b/pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/LogSv.java index 2ea768b..5a46afb 100644 --- a/pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/LogSv.java +++ b/pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/LogSv.java @@ -17,7 +17,6 @@ @Autowired private BaLogMapper dao; - /** * 寰楀埌鏃ュ織 * @@ -46,4 +45,4 @@ rsVo.obj = this.dao.selectSome(params) ; return rsVo ; } -} +} \ No newline at end of file diff --git a/pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/QueryVo.java b/pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/QueryVo.java index 735b946..8fab024 100644 --- a/pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/QueryVo.java +++ b/pms-parent/pms-web-base/src/main/java/com/dy/pmsBase/log/QueryVo.java @@ -1,6 +1,5 @@ package com.dy.pmsBase.log; - import com.dy.common.webUtil.QueryConditionVo; import lombok.*; @@ -12,5 +11,4 @@ @Builder public class QueryVo extends QueryConditionVo { public String name; -} - +} \ No newline at end of file diff --git a/pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/LoginVo.java b/pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/LoginVo.java index f610db8..b1b32b5 100644 --- a/pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/LoginVo.java +++ b/pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/LoginVo.java @@ -26,5 +26,9 @@ @NotEmpty(message = "瀵嗙爜涓嶈兘涓虹┖") //涓嶈兘涓虹┖涔熶笉鑳戒负null @Length(message = "瀵嗙爜蹇呴』{min}浣�", min = 6, max = 6) public String password ; + + @NotEmpty(message = "楠岃瘉鐮佷笉鑳戒负绌�") //涓嶈兘涓虹┖涔熶笉鑳戒负null + @Length(message = "楠岃瘉鐮佸繀椤粄min}浣�", min = 4, max = 4) + public String captcha ; } diff --git a/pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/SsoCtrl.java b/pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/SsoCtrl.java index 8d16790..35026d1 100644 --- a/pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/SsoCtrl.java +++ b/pms-parent/pms-web-sso/src/main/java/com/dy/sso/busi/SsoCtrl.java @@ -8,6 +8,8 @@ import com.dy.pmsGlobal.pojoBa.BaUser; import com.mysql.cj.util.StringUtils; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -15,7 +17,12 @@ import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; import java.util.Objects; +import java.util.Random; import java.util.UUID; /** @@ -47,14 +54,26 @@ */ @PostMapping(path = "login", consumes = MediaType.APPLICATION_JSON_VALUE)//鍓嶇鎻愪氦json鏁版嵁 @Log("鐢ㄦ埛鐧诲綍(json)") - public BaseResponse<UserVo> login(@RequestBody @Valid LoginVo vo, BindingResult bindingResult) { + public BaseResponse<UserVo> login(@RequestBody @Valid LoginVo vo, + HttpSession session, + BindingResult bindingResult) { try { if(bindingResult != null && bindingResult.hasErrors()){ return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()); } - return this.doLogin(vo) ; + + // 浠嶴ession涓幏鍙栦繚瀛樼殑楠岃瘉鐮� + String sessionCaptcha = (String) session.getAttribute("captcha"); + // 棣栧厛楠岃瘉楠岃瘉鐮� + if (vo.captcha != null && vo.captcha.equalsIgnoreCase(sessionCaptcha)) { + session.removeAttribute("captcha"); + return this.doLogin(vo) ; + } else { + // 楠岃瘉鐮侀敊璇紝杩斿洖鐧诲綍椤甸潰骞舵樉绀洪敊璇俊鎭� + return BaseResponseUtils.buildFail("楠岃瘉鐮侀敊璇�"); + } } catch (Exception e) { - log.error("鏌ヨ涓�涓敤鎴锋暟鎹紓甯�", e); + log.error("鐢ㄦ埛鐧诲綍寮傚父", e); return BaseResponseUtils.buildException(e.getMessage()); } } @@ -66,14 +85,23 @@ */ @PostMapping(path = "loginForm", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)//鍓嶇鎻愪氦form琛ㄥ崟鏁版嵁 @Log("鐢ㄦ埛鐧诲綍(form)") - public BaseResponse<UserVo> loginForm(@RequestBody @Valid LoginVo vo, BindingResult bindingResult){ + public BaseResponse<UserVo> loginForm(@RequestBody @Valid LoginVo vo, HttpSession session,BindingResult bindingResult){ try{ if(bindingResult != null && bindingResult.hasErrors()){ return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()); } - return this.doLogin(vo) ; + // 浠嶴ession涓幏鍙栦繚瀛樼殑楠岃瘉鐮� + String sessionCaptcha = (String) session.getAttribute("captcha"); + // 棣栧厛楠岃瘉楠岃瘉鐮� + if (vo.captcha != null && vo.captcha.equalsIgnoreCase(sessionCaptcha)) { + session.removeAttribute("captcha"); + return this.doLogin(vo) ; + } else { + // 楠岃瘉鐮侀敊璇紝杩斿洖鐧诲綍椤甸潰骞舵樉绀洪敊璇俊鎭� + return BaseResponseUtils.buildFail("楠岃瘉鐮侀敊璇�"); + } } catch (Exception e) { - log.error("鏌ヨ涓�涓敤鎴锋暟鎹紓甯�", e); + log.error("鐢ㄦ埛鐧诲綍寮傚父", e); return BaseResponseUtils.buildException(e.getMessage()); } } @@ -100,6 +128,8 @@ return BaseResponseUtils.buildException(e.getMessage()); } } + + /** * 姝ゆ柟娉曚緵瀛愭ā鍧楃郴缁熻皟鐢紝鎵�浠ヤ笉鍏紑鍦ˋPI鎺ュ彛涓� @@ -187,11 +217,57 @@ } return vo ; } + + /** + * 鐢熸垚鐧诲綍楠岃瘉鐮� + * @param response + * @param session + * @throws IOException + */ + @GetMapping("/captcha") + public void captcha(HttpServletResponse response, HttpSession session) throws IOException { + // 璁剧疆鍝嶅簲鐨勭被鍨嬫牸寮忎负鍥剧墖鏍煎紡 + response.setContentType("image/jpeg"); + // 绂佹鍥惧儚缂撳瓨 + response.setHeader("Pragma", "no-cache"); + response.setHeader("Cache-Control", "no-cache"); + response.setDateHeader("Expires", 0); + + int width = 100, height = 50; + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics g = image.getGraphics(); + // 璁惧畾鑳屾櫙鑹� + g.setColor(Color.WHITE); + g.fillRect(0, 0, width, height); + // 璁惧畾瀛椾綋 + g.setFont(new Font("Arial", Font.BOLD, 30)); + // 闅忔満鐢熸垚楠岃瘉鐮� + String captcha = generateCaptcha(); + // 灏嗛獙璇佺爜瀛樺叆Session + session.setAttribute("captcha", captcha); + // 鍦ㄥ浘鐗囦笂缁樺埗楠岃瘉鐮� + g.setColor(Color.BLACK); + g.drawString(captcha, 15, 35); + g.dispose(); + // 杈撳嚭鍥剧墖 + ImageIO.write(image, "JPEG", response.getOutputStream()); + } + + ///////////////////////////////////////////////////////////////// // // 浠ヤ笅绉佹湁鏂规硶 // ///////////////////////////////////////////////////////////////// + + /** + * 鐢熸垚鍥涗綅闅忔満鏁� + * @return + */ + private String generateCaptcha() { + Random r = new Random(); + return r.nextInt(9000) + 1000 + ""; + } /** * 鐢ㄦ埛鐧诲綍 * @param vo 鐧诲綍鐢ㄦ埛form琛ㄥ崟瀵硅薄 -- Gitblit v1.8.0