上传证书

main
Gitea 5 months ago
parent 0e86c3ffc8
commit 8313a6c122
  1. 5
      jeecg-boot-master/jeecg-module-demo/src/main/java/org/jeecg/modules/demo/awardpersion/entity/AwardPersion.java
  2. 173
      jeecg-boot-master/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/CommonController.java
  3. 2
      jeecg-boot-master/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml
  4. 5
      jeecgboot-vue3-master/src/api/common/api.ts
  5. 260
      jeecgboot-vue3-master/src/components/Form/src/jeecg/components/JImageUploadzs.vue
  6. 23
      jeecgboot-vue3-master/src/utils/common/compUtils.ts
  7. 31
      jeecgboot-vue3-master/src/utils/common/renderUtils.ts
  8. 12
      jeecgboot-vue3-master/src/views/awardpersion/AwardPersion.api.ts
  9. 6
      jeecgboot-vue3-master/src/views/awardpersion/AwardPersion.data.ts
  10. 148
      jeecgboot-vue3-master/src/views/awardpersion/components/AwardPersionForm2.vue
  11. 75
      jeecgboot-vue3-master/src/views/awardpersion/components/AwardPersionModal2.vue

@ -113,4 +113,9 @@ public class AwardPersion implements Serializable {
@ApiModelProperty(value = "学生所属学院")
private String studentorg;
/**上传证书*/
@ApiModelProperty(value = "上传证书")
private String sczs;
}

@ -45,6 +45,9 @@ public class CommonController {
@Value(value = "${jeecg.path.upload}")
private String uploadpath;
@Value(value = "${jeecg.path.uploadzs}")
private String uploadpathzs;
/**
* 本地local miniominio 阿里alioss
*/
@ -126,6 +129,114 @@ public class CommonController {
return result;
}
/**
* 证书上传统一方法
* @param request
* @param response
* @return
*/
@PostMapping(value = "/uploadzs")
public Result<?> uploadzs(HttpServletRequest request, HttpServletResponse response) throws Exception {
Result<?> result = new Result<>();
String savePath = "";
String bizPath = request.getParameter("biz");
//LOWCOD-2580 sys/common/upload接口存在任意文件上传漏洞
if (oConvertUtils.isNotEmpty(bizPath)) {
if(bizPath.contains(SymbolConstant.SPOT_SINGLE_SLASH) || bizPath.contains(SymbolConstant.SPOT_DOUBLE_BACKSLASH)){
throw new JeecgBootException("上传目录bizPath,格式非法!");
}
}
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
// 获取上传文件对象
MultipartFile file = multipartRequest.getFile("file");
if(oConvertUtils.isEmpty(bizPath)){
if(CommonConstant.UPLOAD_TYPE_OSS.equals(uploadType)){
//未指定目录,则用阿里云默认目录 upload
bizPath = "uploadzs";
//result.setMessage("使用阿里云文件上传时,必须添加目录!");
//result.setSuccess(false);
//return result;
}else{
bizPath = "";
}
}
if(CommonConstant.UPLOAD_TYPE_LOCAL.equals(uploadType)){
//update-begin-author:liusq date:20221102 for: 过滤上传文件类型
FileTypeFilter.fileTypeFilter(file);
//update-end-author:liusq date:20221102 for: 过滤上传文件类型
//update-begin-author:lvdandan date:20200928 for:修改JEditor编辑器本地上传
savePath = this.uploadLocalzs(file,bizPath);
//update-begin-author:lvdandan date:20200928 for:修改JEditor编辑器本地上传
/** 富文本编辑器及markdown本地上传时采用返回链接方式
//针对jeditor编辑器如何使 lcaol模式,采用 base64格式存储
String jeditor = request.getParameter("jeditor");
if(oConvertUtils.isNotEmpty(jeditor)){
result.setMessage(CommonConstant.UPLOAD_TYPE_LOCAL);
result.setSuccess(true);
return result;
}else{
savePath = this.uploadLocal(file,bizPath);
}
*/
}else{
//update-begin-author:taoyan date:20200814 for:文件上传改造
savePath = CommonUtils.upload(file, bizPath, uploadType);
//update-end-author:taoyan date:20200814 for:文件上传改造
}
if(oConvertUtils.isNotEmpty(savePath)){
result.setMessage(savePath);
result.setSuccess(true);
}else {
result.setMessage("上传失败!");
result.setSuccess(false);
}
return result;
}
/**
* 本地文件上传
* @param mf 文件
* @param bizPath 自定义路径
* @return
*/
private String uploadLocalzs(MultipartFile mf,String bizPath){
try {
String ctxPath = uploadpathzs;
String fileName = null;
File file = new File(ctxPath + File.separator + bizPath + File.separator );
if (!file.exists()) {
// 创建文件根目录
file.mkdirs();
}
// 获取文件名
String orgName = mf.getOriginalFilename();
orgName = CommonUtils.getFileName(orgName);
if(orgName.indexOf(SymbolConstant.SPOT)!=-1){
fileName = orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."));
}else{
fileName = orgName+ "_" + System.currentTimeMillis();
}
String savePath = file.getPath() + File.separator + fileName;
File savefile = new File(savePath);
FileCopyUtils.copy(mf.getBytes(), savefile);
String dbpath = null;
if(oConvertUtils.isNotEmpty(bizPath)){
dbpath = bizPath + File.separator + fileName;
}else{
dbpath = fileName;
}
if (dbpath.contains(SymbolConstant.DOUBLE_BACKSLASH)) {
dbpath = dbpath.replace(SymbolConstant.DOUBLE_BACKSLASH, SymbolConstant.SINGLE_SLASH);
}
return dbpath;
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return "";
}
/**
* 本地文件上传
* @param mf 文件
@ -267,6 +378,68 @@ public class CommonController {
}
/**
* 预览图片&下载文件
* 请求地址http://localhost:8080/common/static/{user/20190119/e1fe9925bc315c60addea1b98eb1cb1349547719_1547866868179.jpg}
*
* @param request
* @param response
*/
@GetMapping(value = "/staticzs/**")
public void viewzs(HttpServletRequest request, HttpServletResponse response) {
// ISO-8859-1 ==> UTF-8 进行编码转换
String imgPath = extractPathFromPattern(request);
if(oConvertUtils.isEmpty(imgPath) || CommonConstant.STRING_NULL.equals(imgPath)){
return;
}
// 其余处理略
InputStream inputStream = null;
OutputStream outputStream = null;
try {
imgPath = imgPath.replace("..", "").replace("../","");
if (imgPath.endsWith(SymbolConstant.COMMA)) {
imgPath = imgPath.substring(0, imgPath.length() - 1);
}
String filePath = uploadpathzs + File.separator + imgPath;
File file = new File(filePath);
if(!file.exists()){
response.setStatus(404);
throw new RuntimeException("文件["+imgPath+"]不存在..");
}
// 设置强制下载不打开
response.setContentType("application/force-download");
response.addHeader("Content-Disposition", "attachment;fileName=" + new String(file.getName().getBytes("UTF-8"),"iso-8859-1"));
inputStream = new BufferedInputStream(new FileInputStream(filePath));
outputStream = response.getOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
response.flushBuffer();
} catch (IOException e) {
log.error("预览文件失败" + e.getMessage());
response.setStatus(404);
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
}
// /**
// * 下载文件
// * 请求地址:http://localhost:8080/common/download/{user/20190119/e1fe9925bc315c60addea1b98eb1cb1349547719_1547866868179.jpg}

@ -184,6 +184,8 @@ jeecg:
path:
#文件上传根目录 设置
upload: /opt/upFiles
#文件上传根目录 设置
uploadzs: /opt/upFileszs
#webapp文件路径
webapp: /opt/webapp
shiro:

@ -21,6 +21,11 @@ enum Api {
*/
export const uploadUrl = `${baseUploadUrl}/sys/common/upload`;
/**
*
*/
export const uploadUrlzs = `${baseUploadUrl}/sys/common/uploadzs`;
/**
*
* @param params

@ -0,0 +1,260 @@
<template>
<div class="clearfix">
<a-upload
:listType="listType"
accept="image/*"
:multiple="multiple"
:action="uploadUrlzs"
:headers="headers"
:data="{ biz: bizPath }"
v-model:fileList="uploadFileList"
:beforeUpload="beforeUpload"
:disabled="disabled"
@change="handleChange"
@preview="handlePreview"
>
<div v-if="uploadVisible">
<div v-if="listType == 'picture-card'">
<LoadingOutlined v-if="loading" />
<UploadOutlined v-else />
<div class="ant-upload-text">{{ text }}</div>
</div>
<a-button v-if="listType == 'picture'" :disabled="disabled">
<UploadOutlined></UploadOutlined>
{{ text }}
</a-button>
</div>
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel()">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, reactive, watchEffect, computed, unref, watch, onMounted } from 'vue';
import { LoadingOutlined, UploadOutlined } from '@ant-design/icons-vue';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { propTypes } from '/@/utils/propTypes';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { useMessage } from '/@/hooks/web/useMessage';
import { getFileAccessHttpUrlzs, getRandom } from '/@/utils/common/compUtils';
import { uploadUrlzs } from '/@/api/common/api';
import { getToken } from '/@/utils/auth';
const { createMessage, createErrorModal } = useMessage();
export default defineComponent({
name: 'JImageUpload',
components: { LoadingOutlined, UploadOutlined },
inheritAttrs: false,
props: {
//
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
//
listType: {
type: String,
required: false,
default: 'picture-card',
},
//
text: {
type: String,
required: false,
default: '上传',
},
//
bizPath: {
type: String,
required: false,
default: 'temp',
},
//
disabled: {
type: Boolean,
required: false,
default: false,
},
//
fileMax: {
type: Number,
required: false,
default: 1,
},
},
emits: ['options-change', 'change', 'update:value'],
setup(props, { emit, refs }) {
const emitData = ref<any[]>([]);
const attrs = useAttrs();
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
//
const getFileName = (path) => {
if (path.lastIndexOf('\\') >= 0) {
let reg = new RegExp('\\\\', 'g');
path = path.replace(reg, '/');
}
return path.substring(path.lastIndexOf('/') + 1);
};
//token
const headers = ref<object>({
'X-Access-Token': getToken(),
});
//
const loading = ref<boolean>(false);
//
const initTag = ref<boolean>(true);
//
let uploadFileList = ref<any[]>([]);
//
const previewImage = ref<string | undefined>('');
//
const previewVisible = ref<boolean>(false);
//
const multiple = computed(() => {
return props['fileMax'] > 1;
});
//
const uploadVisible = computed(() => {
return uploadFileList.value.length < props['fileMax'];
});
/**
* 监听value变化
*/
watch(
() => props.value,
(val, prevCount) => {
//update-begin---author:liusq ---date:20230601 forissues/556JImageUploadvalue------------
if (val && val instanceof Array) {
val = val.join(',');
}
if (initTag.value == true) {
initFileList(val);
}
},
{ immediate: true }
//update-end---author:liusq ---date:20230601 forissues/556JImageUploadvalue------------
);
/**
* 初始化文件列表
*/
function initFileList(paths) {
if (!paths || paths.length == 0) {
uploadFileList.value = [];
return;
}
let files = [];
let arr = paths.split(',');
arr.forEach((value) => {
let url = getFileAccessHttpUrlzs(value);
files.push({
uid: getRandom(10),
name: getFileName(value),
status: 'done',
url: url,
response: {
status: 'history',
message: value,
},
});
});
uploadFileList.value = files;
}
/**
* 上传前校验
*/
function beforeUpload(file) {
let fileType = file.type;
if (fileType.indexOf('image') < 0) {
createMessage.info('请上传图片');
return false;
}
}
/**
* 文件上传结果回调
*/
function handleChange({ file, fileList, event }) {
initTag.value = false;
uploadFileList.value = fileList;
if (file.status === 'error') {
createMessage.error(`${file.name} 上传失败.`);
}
let fileUrls = [];
//
if (file.status != 'uploading') {
fileList.forEach((file) => {
if (file.status === 'done') {
//update-begin---author:wangshuai ---date:20221121 for[issues/248]使,------------
initTag.value = true;
//update-end---author:wangshuai ---date:20221121 for[issues/248]使,------------
fileUrls.push(file.response.message);
}
});
if (file.status === 'removed') {
handleDelete(file);
}
}
// emitData.value = fileUrls.join(',');
state.value = fileUrls.join(',');
emit('update:value', fileUrls.join(','));
}
/**
* 删除图片
*/
function handleDelete(file) {
//
console.log(file);
}
/**
* 预览图片
*/
function handlePreview(file) {
previewImage.value = file.url || file.thumbUrl;
previewVisible.value = true;
}
function getAvatarView() {
if (uploadFileList.length > 0) {
let url = uploadFileList[0].url;
return getFileAccessHttpUrlzs(url, null);
}
}
function handleCancel() {
previewVisible.value = false;
}
return {
state,
attrs,
previewImage,
previewVisible,
uploadFileList,
multiple,
headers,
loading,
uploadUrlzs,
beforeUpload,
uploadVisible,
handlePreview,
handleCancel,
handleChange,
};
},
});
</script>
<style scoped>
.ant-upload-select-picture-card i {
font-size: 32px;
color: #999;
}
.ant-upload-select-picture-card .ant-upload-text {
margin-top: 8px;
color: #666;
}
</style>

@ -28,6 +28,29 @@ export const getFileAccessHttpUrl = (fileUrl, prefix = 'http') => {
return result;
};
/**
* 访
* @param fileUrl
* @param prefix(http) http/https
*/
export const getFileAccessHttpUrlzs = (fileUrl, prefix = 'http') => {
let result = fileUrl;
try {
if (fileUrl && fileUrl.length > 0 && !fileUrl.startsWith(prefix)) {
//判断是否是数组格式
let isArray = fileUrl.indexOf('[') != -1;
if (!isArray) {
let prefix = `${baseApiUrl}/sys/common/staticzs/`;
// 判断是否已包含前缀
if (!fileUrl.startsWith(prefix)) {
result = `${prefix}${fileUrl}`;
}
}
}
} catch (err) {}
return result;
};
/**
* window.resize
*/

@ -1,6 +1,6 @@
import { h } from 'vue';
import { Avatar, Tag, Tooltip } from 'ant-design-vue';
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
import { getFileAccessHttpUrl,getFileAccessHttpUrlzs } from '/@/utils/common/compUtils';
import { Tinymce } from '/@/components/Tinymce';
import Icon from '/@/components/Icon';
import { getDictItemsByCode } from '/@/utils/dict/index';
@ -83,6 +83,35 @@ const render = {
);
//update-end-author:taoyan date:2022-5-24 for: VUEN-1084 【vue3】online表单测试发现的新问题 41、生成的代码,树默认图大小未改
},
/**
*
* @param text
*/
renderImagezs: ({ text }) => {
if (!text) {
//update-begin-author:taoyan date:2022-5-24 for: VUEN-1084 【vue3】online表单测试发现的新问题 41、生成的代码,树默认图大小未改
return h(
Avatar,
{ shape: 'square', size: 25 },
{
icon: () => h(Icon, { icon: 'ant-design:file-image-outlined', size: 25 }),
}
);
}
let avatarList = text.split(',');
return h(
'span',
avatarList.map((item) => {
return h(Avatar, {
src: getFileAccessHttpUrlzs(item),
shape: 'square',
size: 25,
style: { marginRight: '5px' },
});
})
);
//update-end-author:taoyan date:2022-5-24 for: VUEN-1084 【vue3】online表单测试发现的新问题 41、生成的代码,树默认图大小未改
},
/**
* Tooltip
* @param text

@ -16,7 +16,7 @@ enum Api {
queryCompId = '/annualcompgroup/annualCompGroup/queryCompId',
queryOptions = '/awardpersion/awardPersion/queryOptions',
personalAbilityEvaluation = '/abilityEvaluation/personalAbilityEvaluation/personalAbilityEvaluation2',
sczs='/awardpersion/awardPersion/sczs',
}
@ -91,6 +91,16 @@ export const saveOrUpdate1 = (params, isUpdate) => {
return defHttp.post({ url: url, params }, { isTransformResponse: false });
}
/**
*
* @param params
* @param isUpdate
*/
export const sczs = (params, isUpdate) => {
let url = isUpdate ? Api.sczs : Api.sczs;
return defHttp.post({ url: url, params }, { isTransformResponse: false });
}
// 个人能力量化
export const personalAbilityEvaluation = (params,handleSuccess) => {
createConfirm({

@ -39,6 +39,12 @@ export const columns: BasicColumn[] = [
align: "center",
dataIndex: 'studentname'
},
{
title: '证书',
align: "center",
dataIndex: 'sczs',
customRender: render.renderImagezs,
},
/* {
title: '状态',
align: "center",

@ -0,0 +1,148 @@
<template>
<a-spin :spinning="confirmLoading">
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol">
<a-row>
<a-col :span="12">
<a-form-item label="上传证书" v-bind="validateInfos.sczs" :label-col="{ span: 10, offset: 1 }">
<j-image-upload v-model:value="formData.sczs" :disabled="disabled" />
</a-form-item>
</a-col>
<!-- <a-col :span="24">
<a-form-item label="状态" v-bind="validateInfos.status">
<j-dict-select-tag v-model:value="formData.status" dictCode="sh_status" placeholder="请选择状态" :disabled="disabled"/>
</a-form-item>
</a-col>-->
<!-- <a-col :span="24">
<a-form-item label="奖项" v-bind="validateInfos.awardid">
<a-input v-model:value="formData.awardid" placeholder="请输入奖项" :disabled="disabled"></a-input>
</a-form-item>
</a-col>-->
</a-row>
</a-form>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick, defineProps, computed, onMounted,watch } from 'vue';
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
import JSearchSelect from '/@/components/Form/src/jeecg/components/JSearchSelect.vue';
import { getValueType } from '/@/utils';
import { sczs,queryCompId } from '../AwardPersion.api';
import { Form } from 'ant-design-vue';
import { duplicateValidate } from '/@/utils/helper/validator'
import JImageUpload from '/@/components/Form/src/jeecg/components/JImageUploadzs.vue';
let strst = ref();
let ndbsxm = ref();
let options= ref();
const props = defineProps({
formDisabled: { type: Boolean, default: false },
formData: { type: Object, default: ()=>{} },
formBpm: { type: Boolean, default: true }
});
const formRef = ref();
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
const formData = reactive<Record<string, any>>({
id: '',
sczs: '',
});
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
//
const validatorRules = {
sczs: [{ required: true, message: '请上传证书!' }],
};
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: true });
//
const disabled = computed(()=>{
if(props.formBpm === true){
if(props.formData.disabled === false){
return false;
}else{
return true;
}
}
return props.formDisabled;
});
/**
* 新增
*/
function add(record) {
edit(record);
}
/**
* 编辑
*/
function edit(record) {
nextTick(() => {
resetFields();
//
Object.assign(formData, record);
});
}
/**
* 提交数据
*/
async function submitForm() {
//
await validate();
confirmLoading.value = true;
const isUpdate = ref<boolean>(false);
//
let model = formData;
/* let awardsort = model.awardname;
let awardname = model.awardsort;
model.awardname = awardname;
model.awardsort = awardsort;*/
if (model.id) {
isUpdate.value = true;
}
//
for (let data in model) {
//
if (model[data] instanceof Array) {
let valueType = getValueType(formRef.value.getProps, data);
//
if (valueType === 'string') {
model[data] = model[data].join(',');
}
}
}
await sczs(model, isUpdate.value)
.then((res) => {
if (res.success) {
createMessage.success(res.message);
emit('ok');
} else {
createMessage.warning(res.message);
}
})
.finally(() => {
confirmLoading.value = false;
});
}
defineExpose({
add,
edit,
submitForm,
});
</script>
<style lang="less" scoped>
.antd-modal-form {
min-height: 500px !important;
overflow-y: auto;
padding: 24px 24px 24px 24px;
}
</style>

@ -0,0 +1,75 @@
<template>
<a-modal :title="title" :width="width" :visible="visible" @ok="handleOk" :okButtonProps="{ class: { 'jee-hidden': disableSubmit } }" @cancel="handleCancel" cancelText="关闭">
<AwardPersionForm2 ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></AwardPersionForm2>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineExpose } from 'vue';
import AwardPersionForm2 from './AwardPersionForm2.vue'
const title = ref<string>('');
const width = ref<number>(400);
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
/**
* 新增
*/
function add(record) {
title.value = '上传证书';
visible.value = true;
nextTick(() => {
registerForm.value.add(record);
});
}
/**
* 编辑
* @param record
*/
function edit(record) {
title.value = disableSubmit.value ? '详情' : '编辑';
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
emit('success');
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
visible.value = false;
}
defineExpose({
add,
edit,
disableSubmit,
});
</script>
<style>
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
</style>
Loading…
Cancel
Save