feat:上传插件。

This commit is contained in:
冼子明 2025-03-23 20:16:45 +08:00
parent f5cc7e125a
commit a0496f9200
11 changed files with 286 additions and 93 deletions

View File

@ -1,16 +1,20 @@
package vip.fuck.sm.common.utils;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.system.SystemUtil;
import org.noear.solon.Solon;
public class FileConfig {
public static String getFilePath(){
public static String getFilePath( String... subDir){
String key ;
if(SystemUtil.getOsInfo().isWindows()){
key = "file.path-win";
}else{
key = "file.path-linux";
}
return Solon.cfg().get(key,"/file-path");
String p = Solon.cfg().get(key,"/file-path");
String sub = ObjectUtil.isNotEmpty(subDir) && ObjectUtil.isNotEmpty(subDir[0])
?String.format("/%s",subDir[0]):"";
return String.format("%s%s",p,sub) ;
}
}

View File

@ -5,6 +5,7 @@ import cn.dev33.satoken.annotation.SaMode;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import vip.fuck.sm.common.exception.BusinessException;
import vip.fuck.sm.common.utils.DateUtils;
import vip.fuck.sm.entity.SysFilesEntity;
import vip.fuck.sm.service.SysFilesService;
import io.swagger.annotations.Api;
@ -17,6 +18,7 @@ import org.noear.solon.validation.annotation.Valid;
import org.smartboot.http.server.HttpRequest;
import org.smartboot.http.server.HttpResponse;
import java.util.Date;
import java.util.List;
@ -40,12 +42,13 @@ public class SysFilesController {
@Post
@Mapping("/upload")
@SaCheckPermission(value = {"sysFiles:add", "sysContent:update", "sysContent:add"}, mode = SaMode.OR)
public String add(UploadedFile file, HttpRequest request) {
public SysFilesEntity add(UploadedFile file) {
//判断文件是否空
if (file == null || file.getName() == null || "".equalsIgnoreCase(file.getName().trim())) {
throw new BusinessException("文件为空");
}
return sysFilesService.saveFile(file, request);
String createTime = DateUtils.format(new Date(), DateUtils.DATEPATTERN);
return sysFilesService.saveFile(file,createTime);
}
@ApiOperation(value = "删除")

View File

@ -1,5 +1,6 @@
package vip.fuck.sm.controller;
import cn.dev33.satoken.annotation.SaMode;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@ -14,9 +15,14 @@ import io.swagger.annotations.ApiParam;
import java.util.List;
import org.noear.solon.core.handle.ModelAndView;
import org.noear.solon.core.handle.UploadedFile;
import org.smartboot.http.server.HttpRequest;
import vip.fuck.sm.common.exception.BusinessException;
import vip.fuck.sm.common.utils.DataResult;
import vip.fuck.sm.entity.SysFilesEntity;
import vip.fuck.sm.entity.SysPlugsEntity;
import vip.fuck.sm.service.SysFilesService;
import vip.fuck.sm.service.SysPlugsService;
@ -43,6 +49,14 @@ public class SysPlugsController {
return new ModelAndView("sysplugs/list.html");
}
@ApiOperation(value = "新增")
@Post
@Mapping("sysPlugs/upload")
// @SaCheckPermission(value = {"sysFiles:add", "sysContent:update", "sysContent:add"}, mode = SaMode.OR)
public SysPlugsEntity add(UploadedFile file) throws Exception {
return sysPlugsService.uploadJar(file);
}
@ApiOperation(value = "查询分页数据")
@Post @Mapping ("sysPlugs/listByPage")

View File

@ -1,9 +1,6 @@
package vip.fuck.sm.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -26,8 +23,8 @@ public class SysFilesEntity extends BasePageEntity implements Serializable {
/**
* 主键
*/
@TableId("id")
private String id;
@TableId(value = "id",type = IdType.AUTO)
private Long id;
/**
* URL地址
@ -35,6 +32,9 @@ public class SysFilesEntity extends BasePageEntity implements Serializable {
@TableField("url")
private String url;
@TableField("file_size")
private Long fileSize;
/**
* 创建时间
*/

View File

@ -22,10 +22,9 @@ public interface SysFilesService extends IService<SysFilesEntity> {
* 保存图片返回url
*
* @param file
* @param request
* @return
*/
String saveFile(UploadedFile file, HttpRequest request);
SysFilesEntity saveFile(UploadedFile file,String ... subPath);
/**
* 删除图片

View File

@ -1,6 +1,8 @@
package vip.fuck.sm.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.noear.solon.core.handle.UploadedFile;
import org.smartboot.http.server.HttpRequest;
import vip.fuck.sm.entity.SysPlugsEntity;
/**
@ -12,5 +14,8 @@ import vip.fuck.sm.entity.SysPlugsEntity;
*/
public interface SysPlugsService extends IService<SysPlugsEntity> {
SysPlugsEntity uploadJar(UploadedFile file) throws Exception;
}

View File

@ -1,10 +1,12 @@
package vip.fuck.sm.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.noear.solon.data.annotation.Tran;
import vip.fuck.sm.common.exception.BusinessException;
import vip.fuck.sm.common.utils.DateUtils;
import vip.fuck.sm.common.utils.FileConfig;
@ -43,25 +45,27 @@ public class SysFilesServiceImpl extends ServiceImpl<SysFilesMapper, SysFilesEnt
private String fileSchema;
@Override
public String saveFile(UploadedFile file, HttpRequest request) {
@Tran
public SysFilesEntity saveFile(UploadedFile file,String ... subPath) {
//存储文件夹
String createTime = DateUtils.format(new Date(), DateUtils.DATEPATTERN);
String createTime = ObjectUtil.isNotEmpty(subPath) && ObjectUtil.isNotEmpty(subPath[0])?subPath[0]:"";
String filePath = FileConfig.getFilePath();
String newPath = filePath + File.separator + createTime + File.separator;
String newPath = String.format("%s/%s/",filePath,createTime);
File uploadDirectory = new File(newPath);
if (uploadDirectory.exists()) {
if (!uploadDirectory.isDirectory()) {
uploadDirectory.delete();
}
Assert.isTrue(uploadDirectory.isDirectory(),
()-> new BusinessException("不合法的目录") );
} else {
uploadDirectory.mkdir();
boolean mkdirs = uploadDirectory.mkdirs();
Assert.isTrue(mkdirs,()-> new BusinessException("目录创建失败") );
}
try {
String fileName = file.getName();
//id与filename保持一直删除文件
String fileNameNew = UUID.randomUUID().toString().replace("-", "") + getFileType(fileName);
String newFilePathName = newPath + fileNameNew;
String url = ( "/files/" + createTime + "/" + fileNameNew).replaceAll("/+", "/");
String url = String.format( "/files/%s/%s" , createTime , fileNameNew)
.replaceAll("/+", "/");
//创建输出文件对象
File outFile = new File(newFilePathName);
if(!outFile.exists()){
@ -75,8 +79,9 @@ public class SysFilesServiceImpl extends ServiceImpl<SysFilesMapper, SysFilesEnt
sysFilesEntity.setFileName(fileName);
sysFilesEntity.setFilePath(newFilePathName);
sysFilesEntity.setUrl(url);
sysFilesEntity.setFileSize(file.getContentSize());
this.save(sysFilesEntity);
return url;
return sysFilesEntity;
} catch (Exception e) {
throw new BusinessException("上传文件失败",e);
}

View File

@ -1,15 +1,144 @@
package vip.fuck.sm.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.SolonProps;
import org.noear.solon.annotation.Component;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.AppClassLoader;
import org.noear.solon.core.ExtendLoader;
import org.noear.solon.core.Props;
import org.noear.solon.core.ResourceScanner;
import org.noear.solon.core.handle.UploadedFile;
import org.noear.solon.core.util.ResourceUtil;
import org.noear.solon.core.util.ScanUtil;
import org.noear.solon.data.annotation.Tran;
import org.smartboot.http.server.HttpRequest;
import vip.fuck.sm.common.exception.BusinessException;
import vip.fuck.sm.common.utils.FileConfig;
import vip.fuck.sm.entity.SysFilesEntity;
import vip.fuck.sm.mapper.SysPlugsMapper;
import vip.fuck.sm.entity.SysPlugsEntity;
import vip.fuck.sm.service.SysFilesService;
import vip.fuck.sm.service.SysPlugsService;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
@Component(value = "sysPlugsService",typed = true)
@Slf4j
public class SysPlugsServiceImpl extends ServiceImpl<SysPlugsMapper, SysPlugsEntity> implements SysPlugsService {
@Inject
private SysFilesService sysFilesService;
@Override
@Tran
public SysPlugsEntity uploadJar(UploadedFile file) throws Exception {
//判断文件是否空
if (file == null || file.getName() == null || "".equalsIgnoreCase(file.getName().trim())) {
throw new BusinessException("文件为空");
}
String extension = file.getExtension();
Assert.isTrue("jar".equals(extension),()-> new BusinessException("后缀必须是jar") );
String jarDir = "sysPlugJars";
SysFilesEntity jf = sysFilesService.saveFile(file,jarDir);
Props p = null;
try{
JarFile jarFile = new JarFile(jf.getFilePath());
Assert.isTrue(ObjectUtil.isNotEmpty(jarFile) , ()-> new BusinessException("插件不存在"));
List<String> nameList = new ArrayList<>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
if (!jarEntry.isDirectory()) {
nameList.add(name);
}
}
List<String> solonMetas = nameList.stream().filter( n -> n.startsWith("META-INF/solon")
&& n.endsWith(".properties")).collect(Collectors.toList());
Assert.isTrue(ObjectUtil.isNotEmpty(solonMetas), ()-> new BusinessException("jar文件不是solon插件缺少插件元文件META-INF/solon/*.properties"));
String anyOneMeta = solonMetas.get(0);
boolean hasSolonPlugin;
try{
JarEntry e = jarFile.getJarEntry(anyOneMeta);
InputStream stream = jarFile.getInputStream(e);
Props vsProp = new Props();
vsProp.load(stream);
String pluginStr = vsProp.getProperty("solon.plugin");
hasSolonPlugin = ObjectUtil.isNotEmpty(pluginStr) ;
}catch (Throwable ex){
hasSolonPlugin = false;
}
Assert.isTrue(hasSolonPlugin, ()-> new BusinessException("solon插件jar文件中缺少实现类配置: solon.plugin"));
String PLUGIN_INFO_PATH = "plugin-info.properties";
String P_AUTHOR = "plugin.author";
String P_DESCRIPTION = "plugin.description";
String P_VERSION = "plugin.version";
String P_QUALIFIED = "plugin.qualified";
String P_SITE_URL = "plugin.siteUrl";
String P_DOC_URL = "plugin.docUrl";
List<String> pluginInfoPropNames = nameList.stream().filter( n -> n.equals(PLUGIN_INFO_PATH)).collect(Collectors.toList());
Assert.isTrue(ObjectUtil.isNotEmpty(pluginInfoPropNames) , ()-> new BusinessException("插件缺少描述文件: "+PLUGIN_INFO_PATH));
String pInfoOne = pluginInfoPropNames.get(0);
p = new Props() ;
boolean pluginInfoPropOk = true;
try{
JarEntry e = jarFile.getJarEntry(pInfoOne);
InputStream stream = jarFile.getInputStream(e);
p.load(stream);
}catch (Throwable ex){
pluginInfoPropOk = false;
}
Assert.isTrue(pluginInfoPropOk , ()-> new BusinessException("插件缺少描述文件: "+PLUGIN_INFO_PATH));
String p_qualified = p.get(P_QUALIFIED);
Assert.isTrue(ObjectUtil.isNotEmpty(p_qualified), ()-> new BusinessException("插件缺少全限定名: "+P_QUALIFIED));
String p_author = p.get(P_AUTHOR);
Assert.isTrue(ObjectUtil.isNotEmpty(p_author), ()-> new BusinessException("插件缺少作者信息: "+P_AUTHOR));
String p_description = p.get(P_DESCRIPTION);
Assert.isTrue(ObjectUtil.isNotEmpty(p_description), ()-> new BusinessException("插件缺少详细说明: "+P_DESCRIPTION));
String p_version = p.get(P_VERSION);
Assert.isTrue(ObjectUtil.isNotEmpty(p_version), ()-> new BusinessException("插件缺少版本编号: "+P_VERSION));
String p_site_url = p.get(P_SITE_URL);
Assert.isTrue(ObjectUtil.isNotEmpty(p_site_url), ()-> new BusinessException("插件缺少官方地址: "+P_SITE_URL));
String p_doc_url = p.get(P_DOC_URL);
Assert.isTrue(ObjectUtil.isNotEmpty(p_doc_url), ()-> new BusinessException("插件缺少文档链接: "+P_DOC_URL));
SysPlugsEntity en = new SysPlugsEntity();
en.setJarSize(jf.getFileSize());
en.setJarPath(jf.getFilePath());
en.setStatus(0);
en.setQualifiedVersion(p_qualified);
en.setAuthor(p_author);
en.setDescription(p_description);
en.setVersion(p_version);
en.setDocUrl(p_doc_url);
en.setSiteUrl(p_site_url);
boolean save = this.save(en);
Assert.isTrue(save,()-> new BusinessException("插件信息保存失败") );
return en;
}catch (Throwable e){
throw e;
}
}
}

View File

@ -219,10 +219,15 @@
}
};
function FrameWH() {
var h = $(window).height() - 41 - 10 - 35 - 10 - 44 - 10;
$("iframe").css("height", h + "px");
};
window.addEventListener('resize',()=>{
FrameWH()
})
</script>

View File

@ -70,7 +70,7 @@
//执行实例
var uploadInst = upload.render({
elem: '#upload' //绑定元素
,ext: 'jpg|png|gif|bmp|jpeg|jar|doc|docx|txt|mp4|mp3'
,exts: 'jpg|png|gif|bmp|jpeg|jar|doc|docx|txt|mp4|mp3'
,url: ctx + 'sysFiles/upload' //上传接口
,done: function(res){
if(res.code){

View File

@ -73,7 +73,7 @@
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button type="submit" class="layui-btn" lay-submit="" lay-filter="submit">保存</button>
<!-- <button type="submit" class="layui-btn" lay-submit="" lay-filter="submit">保存</button>-->
<button class="layui-btn layui-btn-primary" id="btn_cancel">返回</button>
</div>
</div>
@ -84,6 +84,13 @@
<div class="table_div">
<div id="searchParam" sa:hasPermission="sysPlugs:list">
<form class="layui-form ">
<div class="layui-form-item">
<div class="layui-input-inline ">
<button type="button" class="layui-btn" id="upload">
<i class="layui-icon">&#xe67c;</i>上传文件
</button>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-inline">
<input type="text" name="key" class="layui-input" autocomplete="off" placeholder="请输入">
@ -94,14 +101,15 @@
</div>
</form>
</div>
<table class="layui-table" id="showTable" lay-filter="showTable" ></table>
</div>
<script type="text/html" id="toolbar">
<div class="layui-btn-container oper">
<button class="layui-btn layui-btn-sm" lay-event="add" sa:hasPermission="sysPlugs:add">添加</button>
<button class="layui-btn layui-btn-sm" lay-event="batchDeleted" sa:hasPermission="sysPlugs:delete">删除</button>
</div>
</script>
<!--<script type="text/html" id="toolbar">-->
<!-- <div class="layui-btn-container oper">-->
<!-- <button class="layui-btn layui-btn-sm" lay-event="add" sa:hasPermission="sysPlugs:add">添加</button>-->
<!-- <button class="layui-btn layui-btn-sm" lay-event="batchDeleted" sa:hasPermission="sysPlugs:delete">删除</button>-->
<!-- </div>-->
<!--</script>-->
<script type="text/html" id="tool">
<a class="layui-btn layui-btn-xs" lay-event="look">查看</a>
<a class="layui-btn layui-btn-xs" lay-event="edit" sa:hasPermission="sysPlugs:update">编辑</a>
@ -112,11 +120,32 @@
</html>
<script>
layui.use(function () {
var table = layui.table;
var form = layui.form;
var layer = layui.layer;
let table = layui.table;
let form = layui.form;
let layer = layui.layer;
let upload = layui.upload;
//执行实例
let uploadInst = upload.render({
elem: '#upload' //绑定元素
,exts: 'jar'
,url: ctx + 'sysPlugs/upload' //上传接口
,done: function(res){
if(res.code){
layer.msg(res.msg);
}else{
tableIns1.reload();
}
//上传完毕回调
}
,error: function(){
//请求异常回调
layer.msg('上传失败');
}
});
//加载table
var tableIns1 = table.render({
let tableIns1 = table.render({
elem: '#showTable'
, contentType: 'application/json'
, page: true //开启分页
@ -146,49 +175,49 @@
{width: 120, toolbar: "#tool", title: '操作'}
]
]
, toolbar: '#toolbar'
// , toolbar: '#toolbar'
});
//表头工具
table.on('toolbar(showTable)', function(obj){
switch(obj.event){
case 'batchDeleted':
var checkStatus = table.checkStatus(obj.config.id);
var data = checkStatus.data;
if(data.length==0){
layer.msg("请选择要批量删除的列");
}else {
var ids = [];
$(data).each(function (index,item) {
ids.push(item.id);
});
tipDialog(ids);
}
break;
case 'add':
toUnDisabled();
$(".table_div").hide();
$(".operation").show();
$(".title").html("新增");
setTimeout(function () {
form.val('info', {
"test": "test"
, "qualifiedVersion": ""
, "author": ""
, "siteUrl": ""
, "docUrl": ""
, "description": ""
, "version": ""
, "jarPath": ""
, "jarSize": ""
, "status": ""
, "uploadTime": ""
});
}, 200);
break;
};
});
// table.on('toolbar(showTable)', function(obj){
// switch(obj.event){
// case 'batchDeleted':
// var checkStatus = table.checkStatus(obj.config.id);
// var data = checkStatus.data;
// if(data.length==0){
// layer.msg("请选择要批量删除的列");
// }else {
// var ids = [];
// $(data).each(function (index,item) {
// ids.push(item.id);
// });
// tipDialog(ids);
// }
// break;
// case 'add':
// toUnDisabled();
// $(".table_div").hide();
// $(".operation").show();
// $(".title").html("新增");
// setTimeout(function () {
// form.val('info', {
// "test": "test"
// , "qualifiedVersion": ""
// , "author": ""
// , "siteUrl": ""
// , "docUrl": ""
// , "description": ""
// , "version": ""
// , "jarPath": ""
// , "jarSize": ""
// , "status": ""
// , "uploadTime": ""
// });
// }, 200);
// break;
// };
// });
//列操作
table.on('tool(showTable)',function (obj) {
var data = obj.data;
@ -211,7 +240,7 @@
});
//删除
var tipDialog=function (ids) {
let tipDialog=function (ids) {
layer.open({
content: "确定要删除么?",
yes: function(index, layero){
@ -233,23 +262,23 @@
});
//监听保存
form.on('submit(submit)', function(data){
if(data.field.id===undefined || data.field.id===null || data.field.id===""){
CoreUtil.sendPost(ctx + "sysPlugs/add",data.field,function (res) {
$(".table_div").show();
$(".operation").hide();
tableIns1.reload();
});
}else {
CoreUtil.sendPut(ctx + "sysPlugs/update",data.field,function (res) {
$(".table_div").show();
$(".operation").hide();
tableIns1.reload();
});
}
return false;
});
// form.on('submit(submit)', function(data){
// if(data.field.id===undefined || data.field.id===null || data.field.id===""){
// CoreUtil.sendPost(ctx + "sysPlugs/add",data.field,function (res) {
// $(".table_div").show();
// $(".operation").hide();
// tableIns1.reload();
// });
// }else {
// CoreUtil.sendPut(ctx + "sysPlugs/update",data.field,function (res) {
// $(".table_div").show();
// $(".operation").hide();
// tableIns1.reload();
// });
// }
//
// return false;
// });
// 监听搜索操作
form.on('submit(data-search-btn)', function (data) {
@ -264,7 +293,7 @@
});
//回显
var backshow = function (data) {
let backshow = function (data) {
$(".table_div").hide();
$(".operation").show();
$(".title").html("查看");
@ -286,7 +315,7 @@
}
//禁用输入
var toDisabled = function () {
let toDisabled = function () {
$(".oper").hide();
$(".disabled").each(function (index, elem) {
$(elem).attr("disabled", "")
@ -294,7 +323,7 @@
}
//放开输入
var toUnDisabled = function () {
let toUnDisabled = function () {
$(".oper").show();
$(".disabled").each(function (index, elem) {
$(elem).removeAttr("disabled", "")