---添加jar管理测试模块

This commit is contained in:
2025-11-13 19:26:32 +08:00
parent 3f27f8ee04
commit a4132616ba
10 changed files with 621 additions and 3 deletions

View File

@ -0,0 +1,172 @@
package com.xjrsoft.module.dev.controller;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.google.api.client.http.HttpMediaType;
import com.pictc.utils.SpringTools;
import com.pictc.utils.StringUtils;
import com.xjrsoft.common.exception.BusinessException;
import com.xjrsoft.common.model.result.R;
import com.xjrsoft.config.JarConfig;
import com.xjrsoft.module.dev.dto.SysJarInfoDTO;
import com.xjrsoft.module.dev.dto.SysJarReleaseDTO;
import com.xjrsoft.module.dev.entity.SysJarInfo;
import com.xjrsoft.module.dev.service.ISysJarInfoService;
import cn.hutool.core.bean.BeanUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
@RestController
@RequestMapping("/sys/jar")
@Api(value = "/sys/jar",tags = "jar管理")
@AllArgsConstructor
public class SysJarInfoController {
private final ISysJarInfoService service;
private final JarConfig config;
public static final String UPLOAD = "/sys/jar/upload";//上传接口
public static final String DOWLOAD = "/sys/jar/dowload"; //下载接口
public static final String HAS_JAR = "/sys/jar/hasJar"; //是否包含jar
@PostMapping("/upload")
@ApiOperation(value = "上传jar")
public boolean upload(@RequestParam("file") MultipartFile file, @RequestParam(value = "data") String data,HttpServletRequest request){
checkHeader(request);
//上传文件
String name = file.getOriginalFilename();
String suffix = name.substring(name.lastIndexOf(StringPool.DOT));
if(!ISysJarInfoService.UPLOAD_SUFFIX.equals(suffix)) {
throw new BusinessException("文件类型不支持上传", -1);
}
byte[] bytes = Base64.getDecoder().decode(data);
JSONObject jsonObj = JSON.parseObject(new String(bytes,Charset.forName("UTF-8")));
SysJarInfoDTO dto = jsonObj.toJavaObject(SysJarInfoDTO.class);
try {
return service.upload(BeanUtil.toBean(dto,SysJarInfo.class),file.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
@PostMapping("/hasJar")
@ApiOperation(value = "是否包含jar")
public boolean hasJar(@RequestBody SysJarInfoDTO data,HttpServletRequest request){
checkHeader(request);
return service.hasJar(data.getName());
}
@PostMapping("/download/{env}/{name}")
@ApiOperation(value = "下载jar")
public void download(@PathVariable("name") String name,
@PathVariable("env") String env,
HttpServletResponse response,HttpServletRequest request) throws Exception{
checkHeader(request);
byte[] bytes = service.dowload(name,env);
String encodedFileName = URLEncoder.encode(name, StandardCharsets.UTF_8.name());
response.setContentType("application/java-archive"); // 二进制流类型
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName); // filename* 支持 UTF-8 编码
response.setContentLength(bytes.length);
ServletOutputStream stream = response.getOutputStream();
stream.write(bytes);
stream.flush();
}
@PostMapping("/getInfo")
@ApiOperation(value = "下载jar")
public String getInfo(@PathVariable("name") String name,
@PathVariable("env") String env,
HttpServletResponse response) throws Exception{
byte[] bytes = service.dowload(name,env);
String encodedFileName = URLEncoder.encode(name, StandardCharsets.UTF_8.name());
response.setContentType("application/java-archive"); // 二进制流类型
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName); // filename* 支持 UTF-8 编码
response.setContentLength(bytes.length);
ServletOutputStream stream = response.getOutputStream();
stream.write(bytes);
stream.flush();
return encodedFileName;
}
@PostMapping("/release")
@ApiOperation(value = "发布服务")
public R release(@RequestBody SysJarReleaseDTO dto){
return R.ok(service.release(dto.getName(), dto.getEnv(),dto.getFromEnv()));
}
@PostMapping("/sync-release")
@ApiOperation(value = "同步发布")
public boolean sync(@RequestParam("file") MultipartFile file, @RequestParam(value = "data") String data,HttpServletRequest request){
checkHeader(request);
//上传文件
String name = file.getOriginalFilename();
String suffix = name.substring(name.lastIndexOf(StringPool.DOT));
if(!ISysJarInfoService.UPLOAD_SUFFIX.equals(suffix)) {
throw new BusinessException("文件类型不支持上传", -1);
}
byte[] bytes = Base64.getDecoder().decode(data);
JSONObject jsonObj = JSON.parseObject(new String(bytes,Charset.forName("UTF-8")));
SysJarInfoDTO dto = jsonObj.toJavaObject(SysJarInfoDTO.class);
try {
return service.syncRelease(BeanUtil.toBean(dto,SysJarInfo.class),file.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
public void checkHeader(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if(token==null) {
token = request.getHeader("authorization");
}
if(StringUtils.isEmpty(token)) {
throw new BusinessException("认证失败", -1);
}
token = token.substring("Basic ".length());
byte[] decode = Base64.getDecoder().decode(token.getBytes(StandardCharsets.UTF_8));
String mwToken = new String(decode,StandardCharsets.UTF_8);
String[] splits = mwToken.split(":",2);
String username = splits[0];
String password = splits[1];
if(!config.getUsername().equals(username) || !config.getPassword().equals(password)) {
throw new BusinessException("认证失败", -1);
}
}
}

View File

@ -0,0 +1,61 @@
package com.xjrsoft.module.dev.dto;
import java.io.Serializable;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @title: 测试3
* @Author 管理员
* @Date: 2025-09-25
* @Version 1.0
*/
@Data
@Accessors(chain = true)
public class SysJarInfoDTO implements Serializable{
/**
* @Fields {todo}(用一句话描述这个变量表示什么)
*/
private static final long serialVersionUID = 1L;
//名称
private String name;
//是否为开发子项目包
private String custom;
//是否为项目启动包
private String bootStart;
//启动项目依赖库集合
private String libs;
public static SysJarInfoDTO ofCustom(String name) {
return new SysJarInfoDTO().setCustom("Y").setName(name);
}
public static SysJarInfoDTO ofLibJar(String name) {
return new SysJarInfoDTO().setCustom("N").setName(name).setBootStart("N");
}
public static SysJarInfoDTO ofStart(String name,String libs) {
return new SysJarInfoDTO().setCustom("N").setName(name).setBootStart("Y").setLibs(libs);
}
public boolean isCustom() {
return "Y".equals(custom);
}
public boolean isBootStart() {
return "Y".equals(bootStart);
}
}

View File

@ -0,0 +1,26 @@
package com.xjrsoft.module.dev.dto;
import java.io.Serializable;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class SysJarReleaseDTO implements Serializable{
/**
* @Fields {todo}(用一句话描述这个变量表示什么)
*/
private static final long serialVersionUID = 1L;
//应用名称
private String name;
//目标环境
private String env;
//环境来源
private String fromEnv;
}

View File

@ -27,12 +27,34 @@ public class SysJarInfo {
@TableId
private String id;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("版本")
private String vs;
@ApiModelProperty("是否开发包")
private String custom;
private String url;
@ApiModelProperty("是否boot开始")
private String bootStart;
private String fid;
@ApiModelProperty("是否活跃:最新的版本")
private String active;
@ApiModelProperty("jar库列表")
private String libs;
@ApiModelProperty("地址")
private String ossPath;
@ApiModelProperty("jar包大小")
private int size;
@ApiModelProperty("环境")
private String env;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,27 @@
package com.xjrsoft.module.dev.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xjrsoft.module.dev.entity.SysJarInfo;
/**
* @author 张福财
* @date 2025年11月12日 上午10:12:51
* @Description: 系统jar包明细
*/
@Mapper
public interface SysJarInfoMapper extends BaseMapper<SysJarInfo> {
@Select("SELECT vs FROM sys_jar_info WHERE name = #{name} ORDER BY create_time DESC FETCH FIRST 1 ROWS ONLY")
public String getLastVersion(@Param("name") String name);
@Select("SELECT * FROM sys_jar_info WHERE name = #{name} AND active = 'Y' AND env = 'LOCAL'")
public SysJarInfo getLocalActive(@Param("name") String name);
@Select("SELECT * FROM sys_jar_info WHERE boot_start = 'Y' AND name = #{name} AND env = #{env} AND active = #{active}")
public SysJarInfo selectBootStartByNameAndEnv(@Param("name")String name,@Param("env")String env,@Param("active")String active);
}

View File

@ -0,0 +1,58 @@
package com.xjrsoft.module.dev.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xjrsoft.module.dev.entity.SysJarInfo;
/**
* @author 张福财
* @date 2025年11月12日 上午10:14:35
* @Description: jar包管理明细
*/
public interface ISysJarInfoService extends IService<SysJarInfo> {
String UPLOAD_SUFFIX = "jar";
/**
* @Description: 发布环境
* @param env
* @param from 版本来源,可以为空,当为空时,版本来源是当前编译最新版本
* @return void 返回类型
*/
SysJarInfo release(String name,String env,String from);
/**
* @Description: 文件上传
* @param name
* @param custom
* @param buffer
* @return void 返回类型
*/
boolean upload(SysJarInfo info,byte[] buffer);
/**
* @Description: 是否包含jar
* @param name
* @param custom
* @return void 返回类型
*/
boolean hasJar(String name);
/**
* @Description: 是否包含jar
* @param name
* @param custom
* @return void 返回类型
*/
byte[] dowload(String name,String env);
boolean syncRelease(SysJarInfo bean, byte[] bytes);
}

View File

@ -0,0 +1,196 @@
package com.xjrsoft.module.dev.service.impl;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pictc.enums.CommonValue;
import com.xjrsoft.common.factory.CloudStorageService;
import com.xjrsoft.common.factory.OssFactory;
import com.xjrsoft.config.OSSConfig;
import com.xjrsoft.module.dev.entity.SysJarInfo;
import com.xjrsoft.module.dev.mapper.SysJarInfoMapper;
import com.xjrsoft.module.dev.service.ISysJarInfoService;
import com.xjrsoft.module.dev.utils.VersionUtils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
@Service
public class SysJarInfoServiceImpl extends ServiceImpl<SysJarInfoMapper, SysJarInfo> implements ISysJarInfoService {
public static final String SUFFIX = "jar";
/** (非 Javadoc)
* <p>Title: release</p>
* <p>Description: 发布级别 LOCAL => DEV => TEST => STAG|PROD</p>
*/
@Transactional
@Override
public SysJarInfo release(String name,String env, String from) {
String fromEnv = com.pictc.utils.StringUtils.isEmpty(from)?CommonValue.ENV_LOCAL:from;
SysJarInfo fromInfo = baseMapper.selectBootStartByNameAndEnv(name,fromEnv,CommonValue.YES);
SysJarInfo toInfo = baseMapper.selectBootStartByNameAndEnv(name,env,CommonValue.YES);
boolean update = false;
if(toInfo==null) {
toInfo = new SysJarInfo();
toInfo.setId(IdWorker.get32UUID());
toInfo.setName(name);
toInfo.setVs(fromInfo.getVs());
toInfo.setActive(CommonValue.YES);
toInfo.setCustom(fromInfo.getCustom());
toInfo.setBootStart(fromInfo.getBootStart());
} else {
update = true;
//删除旧jar
List<SysJarInfo> libInfos = getCustomLibs(toInfo.getLibs(),toInfo.getEnv());
if(CollectionUtil.isNotEmpty(libInfos)) {
for (SysJarInfo item : libInfos) {
OssFactory.build().delete(item.getOssPath());
baseMapper.deleteById(item.getId());
}
}
}
toInfo.setVs(fromInfo.getVs());
toInfo.setCreateTime(LocalDateTime.now());
toInfo.setEnv(env);
toInfo.setLibs(fromInfo.getLibs());
List<SysJarInfo> libInfos = getCustomLibs(fromInfo.getLibs(),fromEnv);
if(CollectionUtil.isNotEmpty(libInfos)) {
for (SysJarInfo item : libInfos) {
SysJarInfo nitem = BeanUtil.toBean(item,SysJarInfo.class);
nitem.setId(IdWorker.get32UUID());
nitem.setEnv(env);
byte[] buffer = OssFactory.build().download(item.getOssPath());
//复制jar包文件
item.setOssPath(upload(buffer));
item.setCreateTime(LocalDateTime.now());
baseMapper.insert(item);
}
}
if(update) {
baseMapper.updateById(toInfo);
}else {
baseMapper.insert(toInfo);
}
return toInfo;
}
public List<SysJarInfo> getCustomLibs(String libs,String env){
String[] splits = libs.split(",");
List<String> names = Lists.newArrayList();
for (int i = 0; i < splits.length; i++) {
names.add(splits[i]);
}
LambdaQueryWrapper<SysJarInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.in(SysJarInfo::getName,names);
wrapper.eq(SysJarInfo::getCustom,CommonValue.YES);
wrapper.eq(SysJarInfo::getActive,CommonValue.YES);
wrapper.eq(SysJarInfo::getEnv,env);
return baseMapper.selectList(wrapper);
}
@Transactional
@Override
public boolean upload(SysJarInfo info,byte[] buffer) {
info.setId(IdWorker.get32UUID());
info.setEnv(CommonValue.ENV_LOCAL);
info.setActive(CommonValue.YES);
info.setCreateTime(LocalDateTime.now());
if(!CommonValue.YES.equals(info.getBootStart())) {
String url = upload(buffer);
info.setOssPath(url);
info.setSize(buffer.length);
}
SysJarInfo activeInfo = baseMapper.getLocalActive(info.getName());
String lastVersion = null;
if(activeInfo!=null) {
lastVersion = activeInfo.getVs();
activeInfo.setActive(CommonValue.NO);
baseMapper.updateById(activeInfo);
}
info.setVs(VersionUtils.nextVersion(lastVersion));
baseMapper.insert(info);
//清理旧jar包
LambdaQueryWrapper<SysJarInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysJarInfo::getName,info.getName());
queryWrapper.eq(SysJarInfo::getEnv,CommonValue.ENV_LOCAL);
queryWrapper.orderByDesc(SysJarInfo::getCreateTime);
List<SysJarInfo> historys = baseMapper.selectList(queryWrapper);
if(CollectionUtil.isNotEmpty(historys)) {
lastVersion = historys.get(0).getVs();
if(historys.size() > 3) {
for (int i = 3; i < historys.size(); i++) {
SysJarInfo jarInfo = historys.get(i);
OssFactory.build().delete(jarInfo.getOssPath());
baseMapper.deleteById(jarInfo.getId());
}
}
}
return true;
}
private String upload(byte[] buffer) {
CloudStorageService storageService = Objects.requireNonNull(OssFactory.build());
OSSConfig config = storageService.getConfig();
String objectName = storageService.uploadSuffix(buffer,SUFFIX);
String[] split = objectName.split(config.getBucketName() + StringPool.DOT + config.getEndpoint() + StringPool.SLASH);
return split[1];
}
@Override
public boolean hasJar(String name) {
LambdaQueryWrapper<SysJarInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysJarInfo::getName,name);
queryWrapper.eq(SysJarInfo::getCustom,CommonValue.ENV_LOCAL);
queryWrapper.eq(SysJarInfo::getEnv,CommonValue.ENV_LOCAL);
Long count = baseMapper.selectCount(queryWrapper);
return count!=null && count > 0;
}
@Override
public byte[] dowload(String name, String env) {
SysJarInfo info = baseMapper.selectBootStartByNameAndEnv(name,env,CommonValue.YES);
return OssFactory.build().download(info.getOssPath());
}
@Transactional
@Override
public boolean syncRelease(SysJarInfo fromInfo, byte[] bytes) {
SysJarInfo toInfo = baseMapper.selectBootStartByNameAndEnv(fromInfo.getName(),fromInfo.getEnv(),CommonValue.YES);
boolean update = false;
if(toInfo==null) {
toInfo = fromInfo;
} else {
update = true;
}
toInfo.setVs(fromInfo.getVs());
toInfo.setCreateTime(LocalDateTime.now());
toInfo.setLibs(fromInfo.getLibs());
if(!CommonValue.YES.equals(toInfo.getBootStart())) {
OssFactory.build().delete(toInfo.getOssPath());
}
toInfo.setOssPath(upload(bytes));
if(update) {
baseMapper.updateById(toInfo);
}else {
baseMapper.insert(toInfo);
}
return true;
}
}

View File

@ -0,0 +1,30 @@
package com.xjrsoft.module.dev.utils;
import java.text.DecimalFormat;
import com.pictc.utils.StringUtils;
public class VersionUtils {
public static final String PREIX = "V";
public static String nextVersion(String v) {
int _vn = 1;
if(!StringUtils.isEmpty(v)) {
_vn = parseInt(v)+1;
}
DecimalFormat df = new DecimalFormat("000000");
return PREIX+df.format(_vn);
}
public static int parseInt(String v) {
String numStr = v.replaceAll("[^\\d]*","");
return Integer.parseInt(numStr);
}
public static void main(String[] args) {
System.out.println(nextVersion(null));
}
}

View File

@ -0,0 +1,26 @@
package com.pictc.enums;
public interface CommonValue {
static String YES = "Y";
static String NO = "N";
/**
* LOCAL => DEV => TEST => STAG|PROD
* */
static String ENV_LOCAL = "LOCAL"; //本地开发环境
static String ENV_DEV = "DEV"; //开发环境
static String ENV_TEST = "TEST"; //测试环境
static String ENV_STAG = "STAG"; //预生产环境
static String ENV_PROD = "PROD"; //生产环境
static String parseYesNo(boolean val) {
return val?YES:NO;
}
}

View File

@ -12,7 +12,7 @@ import com.xjrsoft.common.constant.GlobalConstant;
* @author: ksy
* @since: 2025/11/5
*/
@FeignClient(value = GlobalConstant.CLIENT_PCITC_MDM_NAME, path = GlobalConstant.CLIENT_API_PRE + GlobalConstant.MDM_MODULE_PREFIX + "/countryRegion")
@FeignClient(value = GlobalConstant.CLIENT_PCITC_MDM_NAME, path = GlobalConstant.CLIENT_API_PRE + GlobalConstant.MDM_MODULE_PREFIX + "/tran/countryRegion")
public interface ICountryRegionClient {
@GetMapping("/getAllTranData")