From e72b3ab95aace19535fe596897822f0e334de5d6 Mon Sep 17 00:00:00 2001 From: “zhuo” <“zhuo@itcast.cn”> Date: 星期五, 11 八月 2023 18:00:23 +0800 Subject: [PATCH] 8-11提交代码 --- cnas-server/src/main/java/com/yuanchu/limslaboratory/service/impl/CnasAnnualPlanServiceImpl.java | 51 +++++++ cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/vo/CnasAnnualPlanVo.java | 3 cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/CnasAnnualPlan.java | 5 cnas-server/src/main/java/com/yuanchu/limslaboratory/mapper/CnasAnnualPlanMapper.java | 2 cnas-server/src/main/java/com/yuanchu/limslaboratory/service/CnasAnnualPlanService.java | 14 ++ cnas-server/src/main/resources/mapper/CnasAnnualPlanMapper.xml | 38 +++--- sys/src/test/java/com/yuanchu/limslaboratory/SysApplicationTests.java | 134 ++++++++++++++++++++++ sys/src/main/resources/application-dev.yml | 2 cnas-server/pom.xml | 21 +++ cnas-server/src/main/java/com/yuanchu/limslaboratory/controller/CnasAnnualPlanController.java | 47 ++++++- 10 files changed, 280 insertions(+), 37 deletions(-) diff --git a/cnas-server/pom.xml b/cnas-server/pom.xml index 3cd7c42..46a16c0 100644 --- a/cnas-server/pom.xml +++ b/cnas-server/pom.xml @@ -29,6 +29,27 @@ <artifactId>hutool-all</artifactId> <version>5.8.12</version> </dependency> + <!--搴曚笅鍥涗釜閮芥槸poi渚濊禆--> + <dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi</artifactId> + <version>4.1.2</version> + </dependency> + <dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi-ooxml</artifactId> + <version>4.1.2</version> + </dependency> + <dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi-ooxml-schemas</artifactId> + <version>4.1.2</version> + </dependency> + <dependency> + <groupId>org.apache.poi</groupId> + <artifactId>poi-scratchpad</artifactId> + <version>4.1.2</version> + </dependency> </dependencies> diff --git a/cnas-server/src/main/java/com/yuanchu/limslaboratory/controller/CnasAnnualPlanController.java b/cnas-server/src/main/java/com/yuanchu/limslaboratory/controller/CnasAnnualPlanController.java index 08f6654..4b410c5 100644 --- a/cnas-server/src/main/java/com/yuanchu/limslaboratory/controller/CnasAnnualPlanController.java +++ b/cnas-server/src/main/java/com/yuanchu/limslaboratory/controller/CnasAnnualPlanController.java @@ -5,6 +5,8 @@ import com.yuanchu.limslaboratory.pojo.CnasAnnualPlan; import com.yuanchu.limslaboratory.pojo.vo.CnasAnnualPlanVo; import com.yuanchu.limslaboratory.service.CnasAnnualPlanService; +import com.yuanchu.limslaboratory.utils.JackSonUtil; +import com.yuanchu.limslaboratory.utils.RedisUtil; import com.yuanchu.limslaboratory.vo.Result; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; @@ -12,7 +14,7 @@ import io.swagger.annotations.ApiOperation; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; -import org.springframework.stereotype.Controller; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.util.Date; @@ -39,12 +41,11 @@ @ApiImplicitParams(value = { @ApiImplicitParam(name = "page", value = "鍒濆椤�", dataTypeClass = Integer.class, required = true), @ApiImplicitParam(name = "pageSize", value = "姣忎竴椤垫暟閲�", dataTypeClass = Integer.class, required = true), - @ApiImplicitParam(name = "beginTime", value = "妫�楠屽紑濮嬫椂闂�", dataTypeClass = Date.class), - @ApiImplicitParam(name = "endTime", value = "妫�楠岀粨鏉熸椂闂�", dataTypeClass = Date.class), + @ApiImplicitParam(name = "planTime", value = "妫�楠屽紑濮嬫椂闂�", dataTypeClass = Date.class), }) @GetMapping("/selectAllList") - public Result selectAllList(Integer page, Integer pageSize, @DateTimeFormat(pattern = "yyyy-MM-dd") Date beginTime, @DateTimeFormat(pattern = "yyyy-MM-dd") Date endTime) { - IPage<CnasAnnualPlanVo> reportPage = cnasAnnualPlanService.selectAllList(new Page(page, pageSize), beginTime, endTime); + public Result selectAllList(Integer page, Integer pageSize, @DateTimeFormat(pattern = "yyyy-MM") Date planTime) { + IPage<CnasAnnualPlanVo> reportPage = cnasAnnualPlanService.selectAllList(new Page(page, pageSize), planTime); Map<String, Object> map = new HashMap<>(); map.put("total", reportPage.getTotal()); map.put("row", reportPage.getRecords()); @@ -56,9 +57,43 @@ @ApiImplicitParams(value = { @ApiImplicitParam(name = "CnasAnnualPlan", value = "瀹℃煡瀵硅薄", dataTypeClass = Integer.class, required = true) }) - public Result addCnasAnnualPlan(@RequestBody CnasAnnualPlan cnasAnnualPlan) { + public Result addCnasAnnualPlan(@RequestHeader("X-Token") String token, @RequestBody CnasAnnualPlan cnasAnnualPlan) throws Exception { + Object object = RedisUtil.get(token); + Map<String, Object> unmarshal = JackSonUtil.unmarshal(JackSonUtil.marshal(object), Map.class); + String name = (String) unmarshal.get("name"); + cnasAnnualPlan.setKeyboarder(name); + //todo:鑾峰彇name鏈夐棶棰� cnasAnnualPlanService.save(cnasAnnualPlan); return Result.success(); } + + @ApiOperation(value = "涓婁紶闄勪欢") + @PostMapping("/addAccessory") + @ApiImplicitParams(value = { + @ApiImplicitParam(name = "auditTime", value = "瀹℃牳鏃ユ湡", dataTypeClass = Date.class, required = true), + @ApiImplicitParam(name = "file", value = "闄勪欢鏂囦欢", dataTypeClass = MultipartFile.class, required = true) + }) + public Result addAccessory(@RequestHeader("X-Token") String token, Date auditTime, MultipartFile file) throws Exception { + //瑙f瀽褰撳墠鐧诲綍鐢ㄦ埛 + Object object = RedisUtil.get(token); + Map<String, Object> unmarshal = JackSonUtil.unmarshal(JackSonUtil.marshal(object), Map.class); + String name = (String) unmarshal.get("name"); + cnasAnnualPlanService.addAccessory(name, auditTime, file); + return Result.success(); + } + + @ApiOperation(value = "鍒犻櫎骞村害璁″垝") + @GetMapping("/deleteCnasAnnualPlan") + @ApiImplicitParams(value = { + @ApiImplicitParam(name = "planId", value = "瀹℃牳鏃ユ湡", dataTypeClass = Integer.class, required = true) + }) + public Result deleteCnasAnnualPlan(Integer planId) { + Integer isDeleteSuccess = cnasAnnualPlanService.deleteCnasAnnualPlan(planId); + if (isDeleteSuccess == 1){ + return Result.success("鍒犻櫎鎴愬姛"); + } else { + return Result.fail("鍒犻櫎澶辫触"); + } + } } diff --git a/cnas-server/src/main/java/com/yuanchu/limslaboratory/mapper/CnasAnnualPlanMapper.java b/cnas-server/src/main/java/com/yuanchu/limslaboratory/mapper/CnasAnnualPlanMapper.java index 1ab268a..c34d271 100644 --- a/cnas-server/src/main/java/com/yuanchu/limslaboratory/mapper/CnasAnnualPlanMapper.java +++ b/cnas-server/src/main/java/com/yuanchu/limslaboratory/mapper/CnasAnnualPlanMapper.java @@ -22,5 +22,5 @@ * 鏌ヨ瀹℃牳璁″垝 * @return */ - IPage<CnasAnnualPlanVo> selectAllList(Page<Object> objectPage, Date beginTime, Date endTime); + IPage<CnasAnnualPlanVo> selectAllList(Page<Object> objectPage, Integer year, Integer month); } diff --git a/cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/CnasAnnualPlan.java b/cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/CnasAnnualPlan.java index 6c55a83..b89903d 100644 --- a/cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/CnasAnnualPlan.java +++ b/cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/CnasAnnualPlan.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.Getter; @@ -23,6 +24,7 @@ */ @Data @TableName("cnas_annual_plan") +@ApiModel(value="CnasAnnualPlan瀵硅薄", description="") public class CnasAnnualPlan implements Serializable { private static final long serialVersionUID = 1L; @@ -64,9 +66,6 @@ @ApiModelProperty(value = "涓嶅悎鏍奸」鐩暟") private Integer count; - - @ApiModelProperty(value = "瀹℃牳鐘舵��,0:鍗冲皢寮�濮�,1:瀹屾垚,2:閫炬湡") - private Integer auditState; @TableLogic(value = "1", delval = "0") @ApiModelProperty(value = "閫昏緫鍒犻櫎 姝e父>=1,鍒犻櫎<=0", hidden = true) diff --git a/cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/vo/CnasAnnualPlanVo.java b/cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/vo/CnasAnnualPlanVo.java index 80840ea..7eb4638 100644 --- a/cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/vo/CnasAnnualPlanVo.java +++ b/cnas-server/src/main/java/com/yuanchu/limslaboratory/pojo/vo/CnasAnnualPlanVo.java @@ -14,4 +14,7 @@ @ApiModelProperty(value = "鏈堜唤") private Integer month; + + @ApiModelProperty(value = "瀹℃牳鐘舵��,0:鍗冲皢寮�濮�,1:瀹屾垚,2:閫炬湡") + private Integer auditState; } diff --git a/cnas-server/src/main/java/com/yuanchu/limslaboratory/service/CnasAnnualPlanService.java b/cnas-server/src/main/java/com/yuanchu/limslaboratory/service/CnasAnnualPlanService.java index 17c8aea..cdb11f9 100644 --- a/cnas-server/src/main/java/com/yuanchu/limslaboratory/service/CnasAnnualPlanService.java +++ b/cnas-server/src/main/java/com/yuanchu/limslaboratory/service/CnasAnnualPlanService.java @@ -5,6 +5,7 @@ import com.yuanchu.limslaboratory.pojo.CnasAnnualPlan; import com.baomidou.mybatisplus.extension.service.IService; import com.yuanchu.limslaboratory.pojo.vo.CnasAnnualPlanVo; +import org.springframework.web.multipart.MultipartFile; import java.util.Date; @@ -22,5 +23,16 @@ * 鏌ヨ瀹℃牳璁″垝 * @return */ - IPage<CnasAnnualPlanVo> selectAllList(Page<Object> objectPage, Date beginTime, Date endTime); + IPage<CnasAnnualPlanVo> selectAllList(Page<Object> objectPage, Date planTime); + + /** + * 涓婁紶闄勪欢 + */ + void addAccessory(String name, Date auditTime, MultipartFile file); + + /** + * 鍒犻櫎骞村害璁″垝 + * @return + */ + Integer deleteCnasAnnualPlan(Integer planId); } diff --git a/cnas-server/src/main/java/com/yuanchu/limslaboratory/service/impl/CnasAnnualPlanServiceImpl.java b/cnas-server/src/main/java/com/yuanchu/limslaboratory/service/impl/CnasAnnualPlanServiceImpl.java index c797363..397621f 100644 --- a/cnas-server/src/main/java/com/yuanchu/limslaboratory/service/impl/CnasAnnualPlanServiceImpl.java +++ b/cnas-server/src/main/java/com/yuanchu/limslaboratory/service/impl/CnasAnnualPlanServiceImpl.java @@ -2,6 +2,7 @@ import cn.hutool.core.date.DateUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -11,6 +12,7 @@ import com.yuanchu.limslaboratory.service.CnasAnnualPlanService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.util.Date; @@ -31,19 +33,60 @@ /** * 鏌ヨ瀹℃牳璁″垝 + * * @return */ @Override - public IPage<CnasAnnualPlanVo> selectAllList(Page<Object> objectPage, Date beginTime, Date endTime) { - IPage<CnasAnnualPlanVo> page = cnasAnnualPlanMapper.selectAllList(objectPage, beginTime, endTime); + public IPage<CnasAnnualPlanVo> selectAllList(Page<Object> objectPage, Date planTime) { + //鍒ゆ柇鏄惁鏈夋棩鏈� + Integer yearTime = null; + Integer monthTime = null; + if (planTime != null) { + yearTime = DateUtil.year(planTime); + monthTime = DateUtil.month(planTime) + 2; + } + IPage<CnasAnnualPlanVo> page = cnasAnnualPlanMapper.selectAllList(objectPage, yearTime, monthTime); page.getRecords().forEach(cnasAnnualPlanVo -> { //鑾峰彇璁″垝鏃堕棿 Date time = cnasAnnualPlanVo.getPlanTime(); - //娣诲姞骞� + //娣诲姞骞存湀 cnasAnnualPlanVo.setYear(DateUtil.year(time)); - //娣诲姞鏈� cnasAnnualPlanVo.setMonth(DateUtil.month(time) + 1); + //鍒ゆ柇瀹℃牳鐘舵�� + //鑾峰彇褰撳墠鏃堕棿 + Date nowDate = new Date(); + //鑾峰彇褰撳墠鐨勫勾鏈� + int year = DateUtil.year(nowDate); + int month = DateUtil.month(nowDate) + 1; + if (cnasAnnualPlanVo.getAuditTime() == null && month > cnasAnnualPlanVo.getMonth() || year > cnasAnnualPlanVo.getYear()) { + cnasAnnualPlanVo.setAuditState(2); + } else if (cnasAnnualPlanVo.getAuditTime() != null) { + cnasAnnualPlanVo.setAuditState(1); + } else { + cnasAnnualPlanVo.setAuditState(0); + } }); return page; } + + /** + * 涓婁紶闄勪欢 + */ + @Override + public void addAccessory(String name, Date auditTime, MultipartFile file) { + //todo: 涓婁紶闄勪欢鏈畬鎴� + } + + /** + * 鍒犻櫎骞村害璁″垝 + * + * @return + */ + @Override + public Integer deleteCnasAnnualPlan(Integer planId) { + LambdaUpdateWrapper<CnasAnnualPlan> updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(CnasAnnualPlan::getId, planId); + updateWrapper.set(CnasAnnualPlan::getState, 0); + return cnasAnnualPlanMapper.update(new CnasAnnualPlan(), updateWrapper); + } } diff --git a/cnas-server/src/main/resources/mapper/CnasAnnualPlanMapper.xml b/cnas-server/src/main/resources/mapper/CnasAnnualPlanMapper.xml index 0b9e5f3..8f53657 100644 --- a/cnas-server/src/main/resources/mapper/CnasAnnualPlanMapper.xml +++ b/cnas-server/src/main/resources/mapper/CnasAnnualPlanMapper.xml @@ -4,26 +4,26 @@ <!--鏌ヨ瀹℃牳璁″垝--> <select id="selectAllList" resultType="com.yuanchu.limslaboratory.pojo.vo.CnasAnnualPlanVo"> select id, - audit_type, - department, - audit_purpose, - audit_leader, - audit_emp, - audit_pursuant, - audit_scope, - write_user, - keyboarder, - plan_time, - count, - audit_state, - state, - audit_time, - create_time, - update_time + audit_type, + department, + audit_purpose, + audit_leader, + audit_emp, + audit_pursuant, + audit_scope, + write_user, + keyboarder, + plan_time, + count, + state, + audit_time, + update_time from cnas_annual_plan where state = 1 - <if test="beginTime != null and endTime != null"> - and plan_time between #{beginTime} and #{endTime} - </if> + <if test="year != null and month != null"> + and year(plan_time) = #{year} + and month(plan_time) = #{month} + </if> + order by id desc </select> </mapper> diff --git a/sys/src/main/resources/application-dev.yml b/sys/src/main/resources/application-dev.yml index c84fc36..77eddc6 100644 --- a/sys/src/main/resources/application-dev.yml +++ b/sys/src/main/resources/application-dev.yml @@ -65,7 +65,7 @@ # redis鏁版嵁搴撶储寮�(榛樿涓�0)锛屾垜浠娇鐢ㄧ储寮曚负3鐨勬暟鎹簱锛岄伩鍏嶅拰鍏朵粬鏁版嵁搴撳啿绐� database: 0 # redis鏈嶅姟鍣ㄥ湴鍧�锛堥粯璁や负localhost锛� - host: localhost + host: 192.168.110.209 # redis绔彛锛堥粯璁や负6379锛� port: 6379 # redis璁块棶瀵嗙爜锛堥粯璁や负绌猴級 diff --git a/sys/src/test/java/com/yuanchu/limslaboratory/SysApplicationTests.java b/sys/src/test/java/com/yuanchu/limslaboratory/SysApplicationTests.java index f3671e0..d1146ea 100644 --- a/sys/src/test/java/com/yuanchu/limslaboratory/SysApplicationTests.java +++ b/sys/src/test/java/com/yuanchu/limslaboratory/SysApplicationTests.java @@ -1,11 +1,24 @@ package com.yuanchu.limslaboratory; + import com.yuanchu.limslaboratory.pojo.vo.PlanVo; import com.yuanchu.limslaboratory.service.PlanService; +import com.yuanchu.limslaboratory.service.UserService; +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.extractor.WordExtractor; +import org.apache.poi.xwpf.extractor.XWPFWordExtractor; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import javax.annotation.Resource; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @SpringBootTest class SysApplicationTests { @@ -13,16 +26,133 @@ @Resource private PlanService planService; + @Resource + private UserService userService; + @Test void contextLoads() { String newString = String.format("%06d", 77); System.out.println("newString === " + newString); } + @Test void TT() { - List<PlanVo> planVos = planService.selectAllPlan(null, null, null, null); - planVos.forEach(System.out::println); + Map<String, Object> userInfo = userService.getUserInfo("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50IjoiYzhiMWFhYWNlYzM2NmMyNGU1ZDE4YzdlZWE5ZTU1MWIiLCJleHAiOjE2OTE3Mzk4MjV9.IZyU5jhTzpxedmmL25dhpkzQS7hth7gt-bzCx9fZyOk"); + System.out.println(userInfo.get("name")); + } + + @Test + void uploading() { + // 鏇挎崲涓篸oc鎴杁ocx鏂囦欢鐨勮矾寰� + String filePath = "D:\\20892\\desktop\\QR-14-01-02+++鍐呴儴瀹℃牳骞村害璁″垝.doc"; + String info = ""; + try { + FileInputStream fis = new FileInputStream(filePath); + if (filePath.endsWith(".doc")) { + // 璇诲彇doc鏂囦欢 + HWPFDocument doc = new HWPFDocument(fis); + WordExtractor docExtractor = new WordExtractor(doc); + String text = docExtractor.getText(); + System.out.println("doc:"); + info = text.trim(); + docExtractor.close(); + } else if (filePath.endsWith(".docx")) { + // 璇诲彇docx鏂囦欢 + XWPFDocument docx = new XWPFDocument(fis); + XWPFWordExtractor docxExtractor = new XWPFWordExtractor(docx); + String text = docxExtractor.getText(); + System.out.println("docx:"); + info = text.trim(); + docxExtractor.close(); + } else { + System.out.println("涓嶆槸word鏂囦欢"); + } + fis.close(); + } catch (Exception e) { + e.printStackTrace(); + } + List<String> infoList = new ArrayList<String>(); + String[] split = info.split("\n"); + for (int i = 0; i < split.length; i++) { + System.out.println("======>" + split[i]); + if (i > 2) { + infoList.add(split[i]); + } + } + Map<String, String> result = new HashMap<>(); + //瀹㈡埛鍗曚綅/椤圭洰鍚嶇О + String[] proAndUnit = infoList.get(0).split("\t"); + for (int i = 0; i < proAndUnit.length; i++) { + if (i == 1) { + result.put("unitName", proAndUnit[i]); + } + if (i == 3) { + result.put("projectName", proAndUnit[i]); + } + } + //濉〃浜哄鍚�/鑱屼綅/鑱旂郴鐢佃瘽/閭紪 + String npty = infoList.get(1); + String nameAndDate = npty.split("form")[1].split("鑱屼綅")[0]; + //濮撳悕/鏃ユ湡 + result.put("nameAndDate", nameAndDate.trim()); + //鑱屼綅 + String post = npty.split("Posts")[1].split("鑱旂郴鐢佃瘽")[0]; + result.put("post", post.trim()); + //鑱旂郴鐢佃瘽 + String phone = npty.split("number")[1].split("閭紪")[0]; + result.put("telephone", phone); + //閭紪 + String email = npty.split("閭紪")[1].split("\t")[1]; + result.put("email", email); + //鏈嶅姟鎬佸害 + String replace = infoList.get(2).replace(" ", "").replace("\t", ""); + System.out.println(replace); + String[] split1 = replace.split("鈽�"); + for (int i = 0; i < split1.length; i++) { + System.out.println(split1[i]); + } + result.forEach((k, v) -> { + System.out.println("k======>" + k); + System.out.println("v======>" + v); + }); + } + + @Test + void upload() throws Exception { + String filePath = "D:\\20892\\desktop\\QR-14-01-02+++鍐呴儴瀹℃牳骞村害璁″垝.doc"; + + String info = ""; + + FileInputStream fis = new FileInputStream(filePath); + if (filePath.endsWith(".doc")) { + // 璇诲彇doc鏂囦欢 + HWPFDocument doc = new HWPFDocument(fis); + WordExtractor docExtractor = new WordExtractor(doc); + String text = docExtractor.getText(); + System.out.println("doc:"); + info = text.trim(); + docExtractor.close(); + } else if (filePath.endsWith(".docx")) { + // 璇诲彇docx鏂囦欢 + XWPFDocument docx = new XWPFDocument(fis); + XWPFWordExtractor docxExtractor = new XWPFWordExtractor(docx); + String text = docxExtractor.getText(); + System.out.println("docx:"); + info = text.trim(); + docxExtractor.close(); + } else { + System.out.println("涓嶆槸word鏂囦欢"); + } +// System.out.println(info); + List<String> infoList = new ArrayList<>(); + String[] split = info.split("\n"); + for (int i = 0; i < split.length; i++) { + System.out.println("======>" + split[i]); + if (i > 2) { + infoList.add(split[i]); + } + } } } -- Gitblit v1.9.3