diff --git a/flex-ui/src/api/system/oss.js b/flex-ui/src/api/system/oss.js
new file mode 100644
index 0000000..5cb1b01
--- /dev/null
+++ b/flex-ui/src/api/system/oss.js
@@ -0,0 +1,26 @@
+import request from '@/utils/request';
+
+// 查询OSS对象存储列表
+export function listOss(query) {
+  return request({
+    url: '/resource/oss/list',
+    method: 'get',
+    params: query
+  });
+}
+
+// 查询OSS对象基于id串
+export function listByIds(ossId) {
+  return request({
+    url: '/resource/oss/listByIds/' + ossId,
+    method: 'get'
+  });
+}
+
+// 删除OSS对象存储
+export function delOss(ossId) {
+  return request({
+    url: '/resource/oss/' + ossId,
+    method: 'delete'
+  });
+}
diff --git a/flex-ui/src/api/system/ossConfig.js b/flex-ui/src/api/system/ossConfig.js
new file mode 100644
index 0000000..29a8549
--- /dev/null
+++ b/flex-ui/src/api/system/ossConfig.js
@@ -0,0 +1,58 @@
+import request from '@/utils/request';
+
+// 查询对象存储配置列表
+export function listOssConfig(query) {
+  return request({
+    url: '/resource/oss/config/list',
+    method: 'get',
+    params: query
+  });
+}
+
+// 查询对象存储配置详细
+export function getOssConfig(ossConfigId) {
+  return request({
+    url: '/resource/oss/config/' + ossConfigId,
+    method: 'get'
+  });
+}
+
+// 新增对象存储配置
+export function addOssConfig(data) {
+  return request({
+    url: '/resource/oss/config',
+    method: 'post',
+    data: data
+  });
+}
+
+// 修改对象存储配置
+export function updateOssConfig(data) {
+  return request({
+    url: '/resource/oss/config',
+    method: 'put',
+    data: data
+  });
+}
+
+// 删除对象存储配置
+export function delOssConfig(ossConfigId) {
+  return request({
+    url: '/resource/oss/config/' + ossConfigId,
+    method: 'delete'
+  });
+}
+
+// 对象存储状态修改
+export function changeOssConfigStatus(ossConfigId, status, configKey) {
+  const data = {
+    ossConfigId,
+    status,
+    configKey
+  };
+  return request({
+    url: '/resource/oss/config/changeStatus',
+    method: 'put',
+    data: data
+  });
+}
diff --git a/flex-ui/src/api/system/user.js b/flex-ui/src/api/system/user.js
index f2f76ef..7064297 100644
--- a/flex-ui/src/api/system/user.js
+++ b/flex-ui/src/api/system/user.js
@@ -104,6 +104,7 @@ export function updateUserPwd(oldPassword, newPassword) {
 export function uploadAvatar(data) {
   return request({
     url: '/system/user/profile/avatar',
+    headers: { 'Content-Type': 'multipart/form-data' },
     method: 'post',
     data: data
   })
diff --git a/flex-ui/src/components/FileUpload/index.vue b/flex-ui/src/components/FileUpload/index.vue
index 16e1fb0..383f526 100644
--- a/flex-ui/src/components/FileUpload/index.vue
+++ b/flex-ui/src/components/FileUpload/index.vue
@@ -39,7 +39,8 @@
 </template>
 
 <script setup>
-import { getToken } from "@/utils/auth";
+import { listByIds, delOss } from "@/api/system/oss";
+import { globalHeaders } from "@/utils/request";
 
 const props = defineProps({
   modelValue: [String, Object, Array],
@@ -56,7 +57,7 @@ const props = defineProps({
   // 文件类型, 例如['png', 'jpg', 'jpeg']
   fileType: {
     type: Array,
-    default: () => ["doc", "xls", "ppt", "txt", "pdf"],
+    default: () => ["doc","docx", "xls", "xlsx", "ppt", "txt", "pdf"],
   },
   // 是否显示提示
   isShowTip: {
@@ -66,27 +67,34 @@ const props = defineProps({
 });
 
 const { proxy } = getCurrentInstance();
-const emit = defineEmits();
+const emit = defineEmits(['update:modelValue']);
 const number = ref(0);
 const uploadList = ref([]);
 const baseUrl = import.meta.env.VITE_APP_BASE_API;
-const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传文件服务器地址
-const headers = ref({ Authorization: "Bearer " + getToken() });
+const uploadFileUrl = ref(baseUrl + "/resource/oss/upload"); // 上传文件服务器地址
+const headers = ref(globalHeaders());
 const fileList = ref([]);
 const showTip = computed(
   () => props.isShowTip && (props.fileType || props.fileSize)
 );
 
-watch(() => props.modelValue, val => {
+watch(() => props.modelValue, async val => {
   if (val) {
     let temp = 1;
     // 首先将值转为数组
-    const list = Array.isArray(val) ? val : props.modelValue.split(',');
+    let list = [];
+    if (Array.isArray(val)) {
+      list = val;
+    } else {
+      const res = await listByIds(val)
+      list = res.data.map((oss) => {
+        const data = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
+        return data;
+      });
+    }
     // 然后将数组转为对象数组
     fileList.value = list.map(item => {
-      if (typeof item === "string") {
-        item = { name: item, url: item };
-      }
+      item = { name: item.name, url: item.url, ossId: item.ossId };
       item.uid = item.uid || new Date().getTime() + temp++;
       return item;
     });
@@ -134,7 +142,7 @@ function handleUploadError(err) {
 // 上传成功回调
 function handleUploadSuccess(res, file) {
   if (res.code === 200) {
-    uploadList.value.push({ name: res.fileName, url: res.fileName });
+    uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
     uploadedSuccessfully();
   } else {
     number.value--;
@@ -147,6 +155,8 @@ function handleUploadSuccess(res, file) {
 
 // 删除文件
 function handleDelete(index) {
+  let ossId = fileList.value[index].ossId;
+  delOss(ossId);
   fileList.value.splice(index, 1);
   emit("update:modelValue", listToString(fileList.value));
 }
@@ -167,7 +177,7 @@ function getFileName(name) {
   if (name.lastIndexOf("/") > -1) {
     return name.slice(name.lastIndexOf("/") + 1);
   } else {
-    return "";
+    return name;
   }
 }
 
@@ -175,12 +185,12 @@ function getFileName(name) {
 function listToString(list, separator) {
   let strs = "";
   separator = separator || ",";
-  for (let i in list) {
-    if (list[i].url) {
-      strs += list[i].url + separator;
+  list.forEach(item => {
+    if (item.ossId) {
+      strs += item.ossId + separator;
     }
-  }
-  return strs != '' ? strs.substr(0, strs.length - 1) : '';
+  })
+  return strs != "" ? strs.substring(0, strs.length - 1) : "";
 }
 </script>
 
diff --git a/flex-ui/src/components/ImageUpload/index.vue b/flex-ui/src/components/ImageUpload/index.vue
index 55dafb8..b0635f4 100644
--- a/flex-ui/src/components/ImageUpload/index.vue
+++ b/flex-ui/src/components/ImageUpload/index.vue
@@ -46,7 +46,8 @@
 </template>
 
 <script setup>
-import { getToken } from "@/utils/auth";
+import { listByIds, delOss } from "@/api/system/oss";
+import {globalHeaders} from "@/utils/request";
 
 const props = defineProps({
   modelValue: [String, Object, Array],
@@ -73,33 +74,40 @@ const props = defineProps({
 });
 
 const { proxy } = getCurrentInstance();
-const emit = defineEmits();
+const emit = defineEmits(['update:modelValue']);
 const number = ref(0);
 const uploadList = ref([]);
 const dialogImageUrl = ref("");
 const dialogVisible = ref(false);
 const baseUrl = import.meta.env.VITE_APP_BASE_API;
-const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload"); // 上传的图片服务器地址
-const headers = ref({ Authorization: "Bearer " + getToken() });
+const uploadImgUrl = ref(baseUrl + "/resource/oss/upload"); // 上传的图片服务器地址
+const headers = ref(globalHeaders());
 const fileList = ref([]);
 const showTip = computed(
   () => props.isShowTip && (props.fileType || props.fileSize)
 );
 
-watch(() => props.modelValue, val => {
+watch(() => props.modelValue,async val => {
   if (val) {
     // 首先将值转为数组
-    const list = Array.isArray(val) ? val : props.modelValue.split(",");
+    let list = [];
+    if (Array.isArray(val)) {
+      list = val;
+    } else {
+      const res = await listByIds(val)
+      list = res.data
+    }
     // 然后将数组转为对象数组
     fileList.value = list.map(item => {
+      // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
+      let itemData;
       if (typeof item === "string") {
-        if (item.indexOf(baseUrl) === -1) {
-          item = { name: baseUrl + item, url: baseUrl + item };
-        } else {
-          item = { name: item, url: item };
-        }
+        itemData = { name: item, url: item };
+      } else {
+        // 此处name使用ossId 防止删除出现重名
+        itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
       }
-      return item;
+      return itemData;
     });
   } else {
     fileList.value = [];
@@ -148,7 +156,7 @@ function handleExceed() {
 // 上传成功回调
 function handleUploadSuccess(res, file) {
   if (res.code === 200) {
-    uploadList.value.push({ name: res.fileName, url: res.fileName });
+    uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
     uploadedSuccessfully();
   } else {
     number.value--;
@@ -163,6 +171,8 @@ function handleUploadSuccess(res, file) {
 function handleDelete(file) {
   const findex = fileList.value.map(f => f.name).indexOf(file.name);
   if (findex > -1 && uploadList.value.length === number.value) {
+    let ossId = fileList.value[findex].ossId;
+    delOss(ossId);
     fileList.value.splice(findex, 1);
     emit("update:modelValue", listToString(fileList.value));
     return false;
@@ -197,11 +207,11 @@ function listToString(list, separator) {
   let strs = "";
   separator = separator || ",";
   for (let i in list) {
-    if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
-      strs += list[i].url.replace(baseUrl, "") + separator;
+    if (undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) {
+      strs += list[i].ossId + separator;
     }
   }
-  return strs != "" ? strs.substr(0, strs.length - 1) : "";
+  return strs != "" ? strs.substring(0, strs.length - 1) : "";
 }
 </script>
 
@@ -210,4 +220,4 @@ function listToString(list, separator) {
 :deep(.hide .el-upload--picture-card) {
     display: none;
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/flex-ui/src/store/modules/user.js b/flex-ui/src/store/modules/user.js
index 434815c..29be1ba 100644
--- a/flex-ui/src/store/modules/user.js
+++ b/flex-ui/src/store/modules/user.js
@@ -36,7 +36,7 @@ const useUserStore = defineStore(
         return new Promise((resolve, reject) => {
           getInfo().then(res => {
             const user = res.user
-            const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
+            const avatar = (user.url == "" || user.url == null) ? defAva :  user.url;
 
             if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
               this.roles = res.roles
diff --git a/flex-ui/src/views/system/oss/config.vue b/flex-ui/src/views/system/oss/config.vue
new file mode 100644
index 0000000..ccf2a31
--- /dev/null
+++ b/flex-ui/src/views/system/oss/config.vue
@@ -0,0 +1,337 @@
+<template>
+  <div class="p-2">
+      <div class="mb-[10px]" v-show="showSearch">
+        <el-card shadow="hover">
+          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
+            <el-form-item label="配置key" prop="configKey">
+              <el-input v-model="queryParams.configKey" placeholder="配置key" clearable style="width: 200px" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="桶名称" prop="bucketName">
+              <el-input v-model="queryParams.bucketName" placeholder="请输入桶名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="是否默认" prop="status">
+              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px">
+                <el-option key="0" label="是" value="0" />
+                <el-option key="1" label="否" value="1" />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:oss:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:oss:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:oss:remove']">
+              删除
+            </el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" :data="ossConfigList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="主建" align="center" prop="ossConfigId" v-if="columns[0].visible" />
+        <el-table-column label="配置key" align="center" prop="configKey" v-if="columns[1].visible" />
+        <el-table-column label="访问站点" align="center" prop="endpoint" v-if="columns[2].visible" width="200" />
+        <el-table-column label="自定义域名" align="center" prop="domain" v-if="columns[3].visible" width="200" />
+        <el-table-column label="桶名称" align="center" prop="bucketName" v-if="columns[4].visible" />
+        <el-table-column label="前缀" align="center" prop="prefix" v-if="columns[5].visible" />
+        <el-table-column label="域" align="center" prop="region" v-if="columns[6].visible" />
+        <el-table-column label="桶权限类型" align="center" prop="accessPolicy" v-if="columns[7].visible">
+          <template #default="scope">
+            <el-tag type="warning" v-if="scope.row.accessPolicy === '0'">private</el-tag>
+            <el-tag type="success" v-if="scope.row.accessPolicy === '1'">public</el-tag>
+            <el-tag type="info" v-if="scope.row.accessPolicy === '2'">custom</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="是否默认" align="center" prop="status" v-if="columns[8].visible">
+          <template #default="scope">
+            <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding">
+          <template #default="scope">
+            <el-tooltip content="修改" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:oss:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:oss:remove']"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改对象存储配置对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
+      <el-form ref="ossConfigFormRef" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="配置key" prop="configKey">
+          <el-input v-model="form.configKey" placeholder="请输入配置key" />
+        </el-form-item>
+        <el-form-item label="访问站点" prop="endpoint">
+          <el-input v-model="form.endpoint" placeholder="请输入访问站点" />
+        </el-form-item>
+        <el-form-item label="自定义域名" prop="domain">
+          <el-input v-model="form.domain" placeholder="请输入自定义域名" />
+        </el-form-item>
+        <el-form-item label="accessKey" prop="accessKey">
+          <el-input v-model="form.accessKey" placeholder="请输入accessKey" />
+        </el-form-item>
+        <el-form-item label="secretKey" prop="secretKey">
+          <el-input v-model="form.secretKey" placeholder="请输入秘钥" show-password />
+        </el-form-item>
+        <el-form-item label="桶名称" prop="bucketName">
+          <el-input v-model="form.bucketName" placeholder="请输入桶名称" />
+        </el-form-item>
+        <el-form-item label="前缀" prop="prefix">
+          <el-input v-model="form.prefix" placeholder="请输入前缀" />
+        </el-form-item>
+        <el-form-item label="是否HTTPS">
+          <el-radio-group v-model="form.isHttps">
+            <el-radio v-for="dict in sys_yes_no" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="桶权限类型">
+          <el-radio-group v-model="form.accessPolicy">
+            <el-radio label="0">private</el-radio>
+            <el-radio label="1">public</el-radio>
+            <el-radio label="2">custom</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="域" prop="region">
+          <el-input v-model="form.region" placeholder="请输入域" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="OssConfig">
+import {
+  listOssConfig,
+  getOssConfig,
+  delOssConfig,
+  addOssConfig,
+  updateOssConfig,
+  changeOssConfigStatus
+} from "@/api/system/ossConfig";
+
+
+
+const { proxy } = getCurrentInstance()
+const { sys_yes_no } = toRefs(proxy?.useDict("sys_yes_no"));
+
+const ossConfigList = ref([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const dialog = reactive({
+  visible: false,
+  title: ''
+});
+
+// 列显隐信息
+const columns = ref([
+  { key: 0, label: `主建`, visible: true },
+  { key: 1, label: `配置key`, visible: false },
+  { key: 2, label: `访问站点`, visible: true },
+  { key: 3, label: `自定义域名`, visible: true },
+  { key: 4, label: `桶名称`, visible: true },
+  { key: 5, label: `前缀`, visible: true },
+  { key: 6, label: `域`, visible: true },
+  { key: 7, label: `桶权限类型`, visible: true },
+  { key: 8, label: `状态`, visible: true }
+]);
+
+
+const initFormData = {
+  ossConfigId: undefined,
+  configKey: '',
+  accessKey: '',
+  secretKey: '',
+  bucketName: '',
+  prefix: '',
+  endpoint: '',
+  domain: '',
+  isHttps: "N",
+  accessPolicy: "1",
+  region: '',
+  status: "1",
+  remark: '',
+}
+const data = reactive({
+  form: { ...initFormData },
+  // 查询参数
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    configKey: '',
+    bucketName: '',
+    status: '',
+  },
+  rules: {
+    configKey: [{ required: true, message: "configKey不能为空", trigger: "blur" },],
+    accessKey: [
+      { required: true, message: "accessKey不能为空", trigger: "blur" },
+      {
+        min: 2,
+        max: 200,
+        message: "accessKey长度必须介于 2 和 100 之间",
+        trigger: "blur",
+      },
+    ],
+    secretKey: [
+      { required: true, message: "secretKey不能为空", trigger: "blur" },
+      {
+        min: 2,
+        max: 100,
+        message: "secretKey长度必须介于 2 和 100 之间",
+        trigger: "blur",
+      },
+    ],
+    bucketName: [
+      { required: true, message: "bucketName不能为空", trigger: "blur" },
+      {
+        min: 2,
+        max: 100,
+        message: "bucketName长度必须介于 2 和 100 之间",
+        trigger: "blur",
+      },
+    ],
+    endpoint: [
+      { required: true, message: "endpoint不能为空", trigger: "blur" },
+      {
+        min: 2,
+        max: 100,
+        message: "endpoint名称长度必须介于 2 和 100 之间",
+        trigger: "blur",
+      },
+    ],
+    accessPolicy: [{ required: true, message: "accessPolicy不能为空", trigger: "blur" }]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询对象存储配置列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listOssConfig(queryParams.value);
+  ossConfigList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+/** 取消按钮 */
+const cancel = () => {
+  dialog.visible = false;
+  reset();
+}
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  proxy.resetForm("ossConfigFormRef");
+}
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  proxy.resetForm("queryFormRef");
+  handleQuery();
+}
+/** 选择条数  */
+const handleSelectionChange = (selection) => {
+  ids.value = selection.map(item => item.ossConfigId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加对象存储配置";
+}
+/** 修改按钮操作 */
+const handleUpdate = async (row) => {
+  reset();
+  const ossConfigId = row?.ossConfigId || ids.value[0];
+  const res = await getOssConfig(ossConfigId);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = "修改对象存储配置";
+}
+/** 提交按钮 */
+const submitForm = () => {
+  proxy.$refs["ossConfigFormRef"].validate(async (valid) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.ossConfigId) {
+        await updateOssConfig(form.value).finally(() => buttonLoading.value = false);
+      } else {
+        await addOssConfig(form.value).finally(() => buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("新增成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+/** 状态修改  */
+const handleStatusChange = async (row) => {
+  let text = row.status === "0" ? "启用" : "停用";
+  try {
+    await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?');
+    await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey);
+    await getList()
+    proxy?.$modal.msgSuccess(text + "成功");
+  } catch { return } finally {
+    row.status = row.status === "0" ? "1" : "0";
+  }
+
+}
+/** 删除按钮操作 */
+const handleDelete = async (row) => {
+  const ossConfigIds = row?.ossConfigId || ids.value;
+  await proxy?.$modal.confirm('是否确认删除OSS配置编号为"' + ossConfigIds + '"的数据项?');
+  loading.value = true;
+  await delOssConfig(ossConfigIds).finally(() => loading.value = false);
+  await getList();
+  proxy?.$modal.msgSuccess("删除成功");
+
+}
+
+onMounted(() => {
+  getList();
+})
+</script>
diff --git a/flex-ui/src/views/system/oss/index.vue b/flex-ui/src/views/system/oss/index.vue
new file mode 100644
index 0000000..1435649
--- /dev/null
+++ b/flex-ui/src/views/system/oss/index.vue
@@ -0,0 +1,330 @@
+<template>
+  <div class="p-2">
+      <div class="mb-[10px]" v-show="showSearch">
+        <el-card shadow="hover">
+          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
+            <el-form-item label="文件名" prop="fileName">
+              <el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable style="width: 200px" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="原名" prop="originalName">
+              <el-input v-model="queryParams.originalName" placeholder="请输入原名" clearable style="width: 200px" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="文件后缀" prop="fileSuffix">
+              <el-input v-model="queryParams.fileSuffix" placeholder="请输入文件后缀" clearable style="width: 200px" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker
+                v-model="dateRangeCreateTime"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
+              ></el-date-picker>
+            </el-form-item>
+            <el-form-item label="服务商" prop="service">
+              <el-input v-model="queryParams.service" placeholder="请输入服务商" clearable style="width: 200px" @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+
+    <el-card shadow="hover">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Upload" @click="handleFile" v-hasPermi="['system:oss:upload']">上传文件</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Upload" @click="handleImage" v-hasPermi="['system:oss:upload']">上传图片</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:oss:remove']">
+              删除
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button
+              :type="previewListResource ? 'danger' : 'warning'"
+              plain
+              @click="handlePreviewListResource(!previewListResource)"
+              v-hasPermi="['system:oss:edit']"
+              >预览开关 :
+              {{
+                previewListResource ? "禁用" : "启用" }}</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="info" plain icon="Operation" @click="handleOssConfig" v-hasPermi="['system:oss:list']">配置管理</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table
+        v-loading="loading"
+        :data="ossList"
+        @selection-change="handleSelectionChange"
+        :header-cell-class-name="handleHeaderClass"
+        @header-click="handleHeaderCLick"
+        v-if="showTable"
+      >
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="对象存储主键" align="center" prop="ossId" v-if="false" />
+        <el-table-column label="文件名" align="center" prop="fileName" />
+        <el-table-column label="原名" align="center" prop="originalName" />
+        <el-table-column label="文件后缀" align="center" prop="fileSuffix" />
+        <el-table-column label="文件展示" align="center" prop="url">
+          <template #default="scope">
+            <ImagePreview
+              v-if="previewListResource && checkFileSuffix(scope.row.fileSuffix)"
+              :width="100"
+              :height="100"
+              :src="scope.row.url"
+              :preview-src-list="[scope.row.url]"
+            />
+            <span v-text="scope.row.url" v-if="!checkFileSuffix(scope.row.fileSuffix) || !previewListResource" />
+          </template>
+        </el-table-column>
+        <el-table-column label="创建时间" align="center" prop="createTime" width="180" sortable="custom">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="上传人" align="center" prop="createByName" />
+        <el-table-column label="服务商" align="center" prop="service" sortable="custom" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-tooltip content="下载" placement="top">
+              <el-button link type="primary" icon="Download" @click="handleDownload(scope.row)" v-hasPermi="['system:oss:download']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:oss:remove']"></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改OSS对象存储对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="ossFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="文件名">
+          <fileUpload v-model="form.file" v-if="type === 0" />
+          <imageUpload v-model="form.file" v-if="type === 1" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Oss">
+import { listOss, delOss } from "@/api/system/oss";
+import ImagePreview from "@/components/ImagePreview/index.vue";
+import { getConfigKey,updateConfigByKey } from "@/api/system/config";
+
+const router = useRouter();
+const { proxy } = getCurrentInstance();
+
+const ossList = ref([]);
+const showTable = ref(true);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+const type = ref(0);
+const previewListResource = ref(true);
+const dateRangeCreateTime = ref(['', '']);
+
+const dialog = reactive({
+  visible: false,
+  title: ''
+});
+
+// 默认排序
+const defaultSort = ref({ prop: 'createTime', order: 'ascending' });
+
+const initFormData = {
+  file: undefined,
+}
+const data = reactive({
+  form: { ...initFormData },
+  // 查询参数
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    fileName: '',
+    originalName: '',
+    fileSuffix: '',
+    createTime: '',
+    service: '',
+    orderByColumn: defaultSort.value.prop,
+    isAsc: defaultSort.value.order
+  },
+  rules: {
+    file: [
+      { required: true, message: "文件不能为空", trigger: "blur" }
+    ]
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询OSS对象存储列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await getConfigKey("sys.oss.previewListResource");
+  previewListResource.value = res?.data === undefined ? true : res.data === 'true';
+  const response = await listOss(proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, "CreateTime"));
+  ossList.value = response.rows;
+  total.value = response.total;
+  loading.value = false;
+  showTable.value = true;
+}
+function checkFileSuffix(fileSuffix) {
+  let arr = ["png", "jpg", "jpeg"];
+  return arr.some(type => {
+    return fileSuffix.indexOf(type) > -1;
+  });
+}
+/** 取消按钮 */
+function cancel() {
+  dialog.visible = false;
+  reset();
+}
+/** 表单重置 */
+function reset() {
+  form.value = { ...initFormData };
+  proxy.resetForm("ossFormRef");
+}
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+/** 重置按钮操作 */
+function resetQuery() {
+  showTable.value = false;
+  dateRangeCreateTime.value = ['', ''];
+  proxy.resetForm("queryFormRef");
+  queryParams.value.orderByColumn = defaultSort.value.prop;
+  queryParams.value.isAsc = defaultSort.value.order;
+  handleQuery();
+}
+/** 选择条数  */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.ossId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+/** 设置列的排序为我们自定义的排序 */
+const handleHeaderClass = ({ column }) => {
+  column.order = column.multiOrder
+}
+/** 点击表头进行排序 */
+const handleHeaderCLick = (column) => {
+  if (column.sortable !== 'custom') {
+    return
+  }
+  switch (column.multiOrder) {
+    case 'descending':
+      column.multiOrder = 'ascending';
+      break;
+    case 'ascending':
+      column.multiOrder = '';
+      break;
+    default:
+      column.multiOrder = 'descending';
+      break;
+  }
+  handleOrderChange(column.property, column.multiOrder)
+}
+const handleOrderChange = (prop, order) => {
+  let orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(",") : [];
+  let isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(",") : [];
+  let propIndex = orderByArr.indexOf(prop)
+  if (propIndex !== -1) {
+    if (order) {
+      //排序里已存在 只修改排序
+      isAscArr[propIndex] = order;
+    } else {
+      //如果order为null 则删除排序字段和属性
+      isAscArr.splice(propIndex, 1);//删除排序
+      orderByArr.splice(propIndex, 1);//删除属性
+    }
+  } else {
+    //排序里不存在则新增排序
+    orderByArr.push(prop);
+    isAscArr.push(order);
+  }
+  //合并排序
+  queryParams.value.orderByColumn = orderByArr.join(",");
+  queryParams.value.isAsc = isAscArr.join(",");
+  getList();
+}
+/** 任务日志列表查询 */
+const handleOssConfig = () => {
+  router.push('/system/oss-config/index')
+}
+/** 文件按钮操作 */
+const handleFile = () => {
+  reset();
+  type.value = 0;
+  dialog.visible = true;
+  dialog.title = "上传文件";
+}
+/** 图片按钮操作 */
+const handleImage = () => {
+  reset();
+  type.value = 1;
+  dialog.visible = true;
+  dialog.title = "上传图片";
+}
+/** 提交按钮 */
+const submitForm = () => {
+  dialog.visible = false;
+  getList();
+}
+/** 下载按钮操作 */
+const handleDownload = (row) => {
+  proxy?.$download.oss(row.ossId)
+}
+/** 用户状态修改  */
+const handlePreviewListResource = async (preview) => {
+  let text = preview ? "启用" : "停用";
+  try {
+    await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
+    await updateConfigByKey("sys.oss.previewListResource", preview);
+    await getList()
+    proxy?.$modal.msgSuccess(text + "成功");
+  } catch { return }
+}
+/** 删除按钮操作 */
+const handleDelete = async (row) => {
+  const ossIds = row?.ossId || ids.value;
+  await proxy?.$modal.confirm('是否确认删除OSS对象存储编号为"' + ossIds + '"的数据项?');
+  loading.value = true;
+  await delOss(ossIds).finally(() => loading.value = false);
+  await getList();
+  proxy?.$modal.msgSuccess("删除成功");
+}
+
+onMounted(() => {
+  getList();
+})
+</script>
diff --git a/flex-ui/src/views/system/user/profile/index.vue b/flex-ui/src/views/system/user/profile/index.vue
index 77dbe8b..3bb80e7 100644
--- a/flex-ui/src/views/system/user/profile/index.vue
+++ b/flex-ui/src/views/system/user/profile/index.vue
@@ -1,6 +1,6 @@
 <template>
   <div class="app-container">
-    <el-row :gutter="20">
+    <el-row :gutter="10">
       <el-col :span="6" :xs="24">
         <el-card class="box-card">
           <template v-slot:header>
@@ -10,7 +10,7 @@
           </template>
           <div>
             <div class="text-center">
-              <userAvatar :user="state.user" />
+              <userAvatar />
             </div>
             <ul class="list-group list-group-striped">
               <li class="list-group-item">
diff --git a/flex-ui/src/views/system/user/profile/userAvatar.vue b/flex-ui/src/views/system/user/profile/userAvatar.vue
index 6950d83..b3b5da1 100644
--- a/flex-ui/src/views/system/user/profile/userAvatar.vue
+++ b/flex-ui/src/views/system/user/profile/userAvatar.vue
@@ -1,53 +1,62 @@
 <template>
-  <div class="user-info-head" @click="editCropper()"><img :src="options.img" title="点击上传头像" class="img-circle img-lg" /></div>
-  <el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened"  @close="closeDialog">
-    <el-row>
-      <el-col :xs="24" :md="12" :style="{height: '350px'}">
-        <vue-cropper
-          ref="cropper"
-          :img="options.img"
-          :info="true"
-          :autoCrop="options.autoCrop"
-          :autoCropWidth="options.autoCropWidth"
-          :autoCropHeight="options.autoCropHeight"
-          :fixedBox="options.fixedBox"
-          @realTime="realTime"
-          v-if="visible"
-        />
-      </el-col>
-      <el-col :xs="24" :md="12" :style="{height: '350px'}">
-        <div class="avatar-upload-preview">
-          <img :src="options.previews.url" :style="options.previews.img"/>
-        </div>
-      </el-col>
-    </el-row>
-    <br/>
-    <el-row>
-      <el-col :lg="2" :md="2">
-        <el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
-          <el-button>
-            选择
-            <el-icon class="el-icon--right"><Upload /></el-icon>
-          </el-button>
-        </el-upload>
-      </el-col>
-      <el-col :lg="{span: 1, offset: 2}" :md="2">
-        <el-button icon="Plus" @click="changeScale(1)"></el-button>
-      </el-col>
-      <el-col :lg="{span: 1, offset: 1}" :md="2">
-        <el-button icon="Minus" @click="changeScale(-1)"></el-button>
-      </el-col>
-      <el-col :lg="{span: 1, offset: 1}" :md="2">
-        <el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
-      </el-col>
-      <el-col :lg="{span: 1, offset: 1}" :md="2">
-        <el-button icon="RefreshRight" @click="rotateRight()"></el-button>
-      </el-col>
-      <el-col :lg="{span: 2, offset: 6}" :md="2">
-        <el-button type="primary" @click="uploadImg()">提 交</el-button>
-      </el-col>
-    </el-row>
-  </el-dialog>
+  <div class="user-info-head" @click="editCropper()">
+    <img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
+    <el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
+      <el-row>
+        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
+          <vue-cropper
+            ref="cropper"
+            :img="options.img"
+            :info="true"
+            :autoCrop="options.autoCrop"
+            :autoCropWidth="options.autoCropWidth"
+            :autoCropHeight="options.autoCropHeight"
+            :fixedBox="options.fixedBox"
+            :outputType="options.outputType"
+            @realTime="realTime"
+            v-if="visible"
+          />
+        </el-col>
+        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
+          <div class="avatar-upload-preview">
+            <img :src="options.previews.url" :style="options.previews.img" />
+          </div>
+        </el-col>
+      </el-row>
+      <br />
+      <el-row>
+        <el-col :lg="2" :md="2">
+          <el-upload
+            action="#"
+            :headers="{'Content-Type':'multipart/form-data'}"
+            :http-request="requestUpload"
+            :show-file-list="false"
+            :before-upload="beforeUpload"
+          >
+            <el-button>
+              选择
+              <el-icon class="el-icon--right"><Upload /></el-icon>
+            </el-button>
+          </el-upload>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 2 }" :md="2">
+          <el-button icon="Plus" @click="changeScale(1)"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="Minus" @click="changeScale(-1)"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="RefreshLeft" @click="rotateLeft()"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 1, offset: 1 }" :md="2">
+          <el-button icon="RefreshRight" @click="rotateRight()"></el-button>
+        </el-col>
+        <el-col :lg="{ span: 2, offset: 6 }" :md="2">
+          <el-button type="primary" @click="uploadImg()">提 交</el-button>
+        </el-col>
+      </el-row>
+    </el-dialog>
+  </div>
 </template>
 
 <script setup>
@@ -71,33 +80,36 @@ const options = reactive({
   autoCropHeight: 200, // 默认生成截图框高度
   fixedBox: true, // 固定截图框大小 不允许改变
   outputType: "png", // 默认生成截图为PNG格式
-  previews: {} //预览数据
+  fileName: "",
+  previews: {}, //预览数据
+  visible: false
 });
 
+
+
 /** 编辑头像 */
 function editCropper() {
   open.value = true;
-};
+}
 /** 打开弹出层结束时的回调 */
 function modalOpened() {
   visible.value = true;
-};
+}
 /** 覆盖默认上传行为 */
-function requestUpload() {
-};
+function requestUpload() {}
 /** 向左旋转 */
 function rotateLeft() {
   proxy.$refs.cropper.rotateLeft();
-};
+}
 /** 向右旋转 */
 function rotateRight() {
   proxy.$refs.cropper.rotateRight();
-};
+}
 /** 图片缩放 */
 function changeScale(num) {
   num = num || 1;
   proxy.$refs.cropper.changeScale(num);
-};
+}
 /** 上传预处理 */
 function beforeUpload(file) {
   if (file.type.indexOf("image/") == -1) {
@@ -107,32 +119,33 @@ function beforeUpload(file) {
     reader.readAsDataURL(file);
     reader.onload = () => {
       options.img = reader.result;
+      options.fileName = file.name;
     };
   }
-};
+}
 /** 上传图片 */
 function uploadImg() {
   proxy.$refs.cropper.getCropBlob(data => {
     let formData = new FormData();
-    formData.append("avatarfile", data);
+    formData.append("avatarfile", data, options.fileName);
     uploadAvatar(formData).then(response => {
       open.value = false;
-      options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl;
+      options.img = response.data.imgUrl;
       userStore.avatar = options.img;
       proxy.$modal.msgSuccess("修改成功");
       visible.value = false;
     });
   });
-};
+}
 /** 实时预览 */
 function realTime(data) {
   options.previews = data;
-};
+}
 /** 关闭窗口 */
 function closeDialog() {
   options.img = userStore.avatar;
   options.visible = false;
-};
+}
 </script>
 
 <style lang='scss' scoped>
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/service/OssService.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/service/OssService.java
new file mode 100644
index 0000000..65dda7c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/service/OssService.java
@@ -0,0 +1,18 @@
+package com.ruoyi.common.core.service;
+
+/**
+ * 通用 OSS服务
+ *
+ * @author Lion Li
+ */
+public interface OssService {
+
+    /**
+     * 通过ossId查询对应的url
+     *
+     * @param ossIds ossId串逗号分隔
+     * @return url串逗号分隔
+     */
+    String selectUrlByIds(String ossIds);
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/pom.xml b/ruoyi-common/ruoyi-common-oss/pom.xml
new file mode 100644
index 0000000..3725533
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.ruoyi</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-oss</artifactId>
+
+    <description>
+        ruoyi-common-oss oss对象存储服务
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.ruoyi</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-s3</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/constant/OssConstant.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/constant/OssConstant.java
new file mode 100644
index 0000000..50a3292
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/constant/OssConstant.java
@@ -0,0 +1,38 @@
+package com.ruoyi.common.oss.constant;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 对象存储常量
+ *
+ * @author Lion Li
+ */
+public interface OssConstant {
+
+    /**
+     * 默认配置KEY
+     */
+    String DEFAULT_CONFIG_KEY = "sys_oss:default_config";
+
+    /**
+     * 预览列表资源开关Key
+     */
+    String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource";
+
+    /**
+     * 系统数据ids
+     */
+    List<Long> SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L);
+
+    /**
+     * 云服务商
+     */
+    String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"};
+
+    /**
+     * https 状态
+     */
+    String IS_HTTPS = "Y";
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/core/OssClient.java
new file mode 100644
index 0000000..d22f540
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/core/OssClient.java
@@ -0,0 +1,262 @@
+package com.ruoyi.common.oss.core;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import com.amazonaws.ClientConfiguration;
+import com.amazonaws.HttpMethod;
+import com.amazonaws.Protocol;
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.client.builder.AwsClientBuilder;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.AmazonS3ClientBuilder;
+import com.amazonaws.services.s3.model.*;
+import com.ruoyi.common.core.utils.DateUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.oss.constant.OssConstant;
+import com.ruoyi.common.oss.entity.UploadResult;
+import com.ruoyi.common.oss.enumd.AccessPolicyType;
+import com.ruoyi.common.oss.enumd.PolicyType;
+import com.ruoyi.common.oss.exception.OssException;
+import com.ruoyi.common.oss.properties.OssProperties;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Date;
+
+/**
+ * S3 存储协议 所有兼容S3协议的云厂商均支持
+ * 阿里云 腾讯云 七牛云 minio
+ *
+ * @author Lion Li
+ */
+public class OssClient {
+
+    private final String configKey;
+
+    private final OssProperties properties;
+
+    private final AmazonS3 client;
+
+    public OssClient(String configKey, OssProperties ossProperties) {
+        this.configKey = configKey;
+        this.properties = ossProperties;
+        try {
+            AwsClientBuilder.EndpointConfiguration endpointConfig =
+                new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
+
+            AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
+            AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
+            ClientConfiguration clientConfig = new ClientConfiguration();
+            if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
+                clientConfig.setProtocol(Protocol.HTTPS);
+            } else {
+                clientConfig.setProtocol(Protocol.HTTP);
+            }
+            AmazonS3ClientBuilder build = AmazonS3Client.builder()
+                .withEndpointConfiguration(endpointConfig)
+                .withClientConfiguration(clientConfig)
+                .withCredentials(credentialsProvider)
+                .disableChunkedEncoding();
+            if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
+                // minio 使用https限制使用域名访问 需要此配置 站点填域名
+                build.enablePathStyleAccess();
+            }
+            this.client = build.build();
+
+            createBucket();
+        } catch (Exception e) {
+            if (e instanceof OssException) {
+                throw e;
+            }
+            throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
+        }
+    }
+
+    public void createBucket() {
+        try {
+            String bucketName = properties.getBucketName();
+            if (client.doesBucketExistV2(bucketName)) {
+                return;
+            }
+            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
+            AccessPolicyType accessPolicy = getAccessPolicy();
+            createBucketRequest.setCannedAcl(accessPolicy.getAcl());
+            client.createBucket(createBucketRequest);
+            client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
+        } catch (Exception e) {
+            throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
+        }
+    }
+
+    public UploadResult upload(byte[] data, String path, String contentType) {
+        return upload(new ByteArrayInputStream(data), path, contentType);
+    }
+
+    public UploadResult upload(InputStream inputStream, String path, String contentType) {
+        if (!(inputStream instanceof ByteArrayInputStream)) {
+            inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
+        }
+        try {
+            ObjectMetadata metadata = new ObjectMetadata();
+            metadata.setContentType(contentType);
+            metadata.setContentLength(inputStream.available());
+            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata);
+            // 设置上传对象的 Acl 为公共读
+            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
+            client.putObject(putObjectRequest);
+        } catch (Exception e) {
+            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
+        }
+        return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
+    }
+
+    public UploadResult upload(File file, String path) {
+        try {
+            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
+            // 设置上传对象的 Acl 为公共读
+            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
+            client.putObject(putObjectRequest);
+        } catch (Exception e) {
+            throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
+        }
+        return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
+    }
+
+    public void delete(String path) {
+        path = path.replace(getUrl() + "/", "");
+        try {
+            client.deleteObject(properties.getBucketName(), path);
+        } catch (Exception e) {
+            throw new OssException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]");
+        }
+    }
+
+    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
+        return upload(data, getPath(properties.getPrefix(), suffix), contentType);
+    }
+
+    public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
+        return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
+    }
+
+    public UploadResult uploadSuffix(File file, String suffix) {
+        return upload(file, getPath(properties.getPrefix(), suffix));
+    }
+
+    /**
+     * 获取文件元数据
+     *
+     * @param path 完整文件路径
+     */
+    public ObjectMetadata getObjectMetadata(String path) {
+        path = path.replace(getUrl() + "/", "");
+        S3Object object = client.getObject(properties.getBucketName(), path);
+        return object.getObjectMetadata();
+    }
+
+    public InputStream getObjectContent(String path) {
+        path = path.replace(getUrl() + "/", "");
+        S3Object object = client.getObject(properties.getBucketName(), path);
+        return object.getObjectContent();
+    }
+
+    public String getUrl() {
+        String domain = properties.getDomain();
+        String endpoint = properties.getEndpoint();
+        String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
+        // 云服务商直接返回
+        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+            if (StringUtils.isNotBlank(domain)) {
+                return header + domain;
+            }
+            return header + properties.getBucketName() + "." + endpoint;
+        }
+        // minio 单独处理
+        if (StringUtils.isNotBlank(domain)) {
+            return header + domain + "/" + properties.getBucketName();
+        }
+        return header + endpoint + "/" + properties.getBucketName();
+    }
+
+    public String getPath(String prefix, String suffix) {
+        // 生成uuid
+        String uuid = IdUtil.fastSimpleUUID();
+        // 文件路径
+        String path = DateUtils.datePath() + "/" + uuid;
+        if (StringUtils.isNotBlank(prefix)) {
+            path = prefix + "/" + path;
+        }
+        return path + suffix;
+    }
+
+
+    public String getConfigKey() {
+        return configKey;
+    }
+
+    /**
+     * 获取私有URL链接
+     *
+     * @param objectKey 对象KEY
+     * @param second    授权时间
+     */
+    public String getPrivateUrl(String objectKey, Integer second) {
+        GeneratePresignedUrlRequest generatePresignedUrlRequest =
+            new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
+                .withMethod(HttpMethod.GET)
+                .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
+        URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
+        return url.toString();
+    }
+
+    /**
+     * 检查配置是否相同
+     */
+    public boolean checkPropertiesSame(OssProperties properties) {
+        return this.properties.equals(properties);
+    }
+
+    /**
+     * 获取当前桶权限类型
+     *
+     * @return 当前桶权限类型code
+     */
+    public AccessPolicyType getAccessPolicy() {
+        return AccessPolicyType.getByType(properties.getAccessPolicy());
+    }
+
+    private static String getPolicy(String bucketName, PolicyType policyType) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
+        builder.append(switch (policyType) {
+            case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n";
+            case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n";
+            default -> "\"s3:GetBucketLocation\"\n";
+        });
+        builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
+        builder.append(bucketName);
+        builder.append("\"\n},\n");
+        if (policyType == PolicyType.READ) {
+            builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
+            builder.append(bucketName);
+            builder.append("\"\n},\n");
+        }
+        builder.append("{\n\"Action\": ");
+        builder.append(switch (policyType) {
+            case WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
+            case READ_WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
+            default -> "\"s3:GetObject\",\n";
+        });
+        builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
+        builder.append(bucketName);
+        builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
+        return builder.toString();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/entity/UploadResult.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/entity/UploadResult.java
new file mode 100644
index 0000000..fd2e7fc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/entity/UploadResult.java
@@ -0,0 +1,24 @@
+package com.ruoyi.common.oss.entity;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 上传返回体
+ *
+ * @author Lion Li
+ */
+@Data
+@Builder
+public class UploadResult {
+
+    /**
+     * 文件路径
+     */
+    private String url;
+
+    /**
+     * 文件名
+     */
+    private String filename;
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/enumd/AccessPolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/enumd/AccessPolicyType.java
new file mode 100644
index 0000000..140f67a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/enumd/AccessPolicyType.java
@@ -0,0 +1,55 @@
+package com.ruoyi.common.oss.enumd;
+
+import com.amazonaws.services.s3.model.CannedAccessControlList;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 桶访问策略配置
+ *
+ * @author 陈賝
+ */
+@Getter
+@AllArgsConstructor
+public enum AccessPolicyType {
+
+    /**
+     * private
+     */
+    PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE),
+
+    /**
+     * public
+     */
+    PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ),
+
+    /**
+     * custom
+     */
+    CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ);
+
+    /**
+     * 桶 权限类型
+     */
+    private final String type;
+
+    /**
+     * 文件对象 权限类型
+     */
+    private final CannedAccessControlList acl;
+
+    /**
+     * 桶策略类型
+     */
+    private final PolicyType policyType;
+
+    public static AccessPolicyType getByType(String type) {
+        for (AccessPolicyType value : values()) {
+            if (value.getType().equals(type)) {
+                return value;
+            }
+        }
+        throw new RuntimeException("'type' not found By " + type);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/enumd/PolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/enumd/PolicyType.java
new file mode 100644
index 0000000..c019d3b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/enumd/PolicyType.java
@@ -0,0 +1,35 @@
+package com.ruoyi.common.oss.enumd;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * minio策略配置
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum PolicyType {
+
+    /**
+     * 只读
+     */
+    READ("read-only"),
+
+    /**
+     * 只写
+     */
+    WRITE("write-only"),
+
+    /**
+     * 读写
+     */
+    READ_WRITE("read-write");
+
+    /**
+     * 类型
+     */
+    private final String type;
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/exception/OssException.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/exception/OssException.java
new file mode 100644
index 0000000..cfc7520
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/exception/OssException.java
@@ -0,0 +1,19 @@
+package com.ruoyi.common.oss.exception;
+
+import java.io.Serial;
+
+/**
+ * OSS异常类
+ *
+ * @author Lion Li
+ */
+public class OssException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public OssException(String msg) {
+        super(msg);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java
new file mode 100644
index 0000000..b723230
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/factory/OssFactory.java
@@ -0,0 +1,65 @@
+package com.ruoyi.common.oss.factory;
+
+import com.ruoyi.common.core.constant.CacheNames;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.json.utils.JsonUtils;
+import com.ruoyi.common.oss.constant.OssConstant;
+import com.ruoyi.common.oss.core.OssClient;
+import com.ruoyi.common.oss.exception.OssException;
+import com.ruoyi.common.oss.properties.OssProperties;
+import com.ruoyi.common.redis.utils.CacheUtils;
+import com.ruoyi.common.redis.utils.RedisUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 文件上传Factory
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class OssFactory {
+
+    private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
+
+    /**
+     * 获取默认实例
+     */
+    public static OssClient instance() {
+        // 获取redis 默认类型
+        String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY);
+        if (StringUtils.isEmpty(configKey)) {
+            throw new OssException("文件存储服务类型无法找到!");
+        }
+        return instance(configKey);
+    }
+
+    /**
+     * 根据类型获取实例
+     */
+    public static OssClient instance(String configKey) {
+        String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
+        if (json == null) {
+            throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
+        }
+        OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
+        // 使用租户标识避免多个租户相同key实例覆盖
+        String key = properties.getTenantId() + ":" + configKey;
+        OssClient client = CLIENT_CACHE.get(key);
+        if (client == null) {
+            CLIENT_CACHE.put(key, new OssClient(configKey, properties));
+            log.info("创建OSS实例 key => {}", configKey);
+            return CLIENT_CACHE.get(key);
+        }
+        // 配置不相同则重新构建
+        if (!client.checkPropertiesSame(properties)) {
+            CLIENT_CACHE.put(key, new OssClient(configKey, properties));
+            log.info("重载OSS实例 key => {}", configKey);
+            return CLIENT_CACHE.get(key);
+        }
+        return client;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/properties/OssProperties.java b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/properties/OssProperties.java
new file mode 100644
index 0000000..8145fb9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/com/ruoyi/common/oss/properties/OssProperties.java
@@ -0,0 +1,63 @@
+package com.ruoyi.common.oss.properties;
+
+import lombok.Data;
+
+/**
+ * OSS对象存储 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+public class OssProperties {
+
+    /**
+     * 租户id
+     */
+    private String tenantId;
+
+    /**
+     * 访问站点
+     */
+    private String endpoint;
+
+    /**
+     * 自定义域名
+     */
+    private String domain;
+
+    /**
+     * 前缀
+     */
+    private String prefix;
+
+    /**
+     * ACCESS_KEY
+     */
+    private String accessKey;
+
+    /**
+     * SECRET_KEY
+     */
+    private String secretKey;
+
+    /**
+     * 存储空间名
+     */
+    private String bucketName;
+
+    /**
+     * 存储区域
+     */
+    private String region;
+
+    /**
+     * 是否https(Y=是,N=否)
+     */
+    private String isHttps;
+
+    /**
+     * 桶权限类型(0private 1public 2custom)
+     */
+    private String accessPolicy;
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java
new file mode 100644
index 0000000..ca118d7
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssConfigController.java
@@ -0,0 +1,124 @@
+package com.ruoyi.system.controller.system;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.ruoyi.common.core.core.domain.R;
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.core.validate.QueryGroup;
+import com.ruoyi.common.web.annotation.RepeatSubmit;
+import com.ruoyi.common.web.core.BaseController;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.orm.core.page.PageQuery;
+import com.ruoyi.common.orm.core.page.TableDataInfo;
+import com.ruoyi.system.domain.bo.SysOssConfigBo;
+import com.ruoyi.system.domain.vo.SysOssConfigVo;
+import com.ruoyi.system.service.ISysOssConfigService;
+import jakarta.annotation.Resource;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 对象存储配置
+ *
+ * @author Lion Li
+ * @author 孤舟烟雨
+ * @author 数据小王子
+ * @date 2023-11-30
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/resource/oss/config")
+public class SysOssConfigController extends BaseController {
+
+    @Resource
+    private final ISysOssConfigService ossConfigService;
+
+    /**
+     * 查询对象存储配置列表
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOssConfigVo> list(SysOssConfigBo bo) {
+        return ossConfigService.queryPageList(bo);
+    }
+
+    /**
+     * 获取对象存储配置详细信息
+     *
+     * @param ossConfigId OSS配置ID
+     */
+    @SaCheckPermission("system:oss:query")
+    @GetMapping("/{ossConfigId}")
+    public R<SysOssConfigVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long ossConfigId) {
+        return R.ok(ossConfigService.queryById(ossConfigId));
+    }
+
+    /**
+     * 新增对象存储配置
+     */
+    @SaCheckPermission("system:oss:add")
+    @Log(title = "对象存储配置", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated @RequestBody SysOssConfigBo bo) {
+        boolean inserted = ossConfigService.insertByBo(bo);
+        if (!inserted) {
+            return R.fail("新增对象存储配置记录失败!");
+        }
+        return R.ok();
+    }
+
+    /**
+     * 修改对象存储配置
+     */
+    @SaCheckPermission("system:oss:edit")
+    @Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated @RequestBody SysOssConfigBo bo) {
+        Boolean updated = ossConfigService.updateByBo(bo);
+        if (!updated) {
+            R.fail("修改对象存储配置记录失败!");
+        }
+        return R.ok();
+    }
+
+    /**
+     * 删除对象存储配置
+     *
+     * @param ossConfigIds OSS配置ID串
+     */
+    @SaCheckPermission("system:oss:remove")
+    @Log(title = "对象存储配置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ossConfigIds}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ossConfigIds) {
+        boolean deleted = ossConfigService.deleteWithValidByIds(List.of(ossConfigIds), true);
+        if (!deleted) {
+            R.fail("删除对象存储配置记录失败!");
+        }
+        return R.ok();
+    }
+
+    /**
+     * 状态修改
+     */
+    @SaCheckPermission("system:oss:edit")
+    @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
+        boolean updated = ossConfigService.updateOssConfigStatus(bo);
+        if (!updated) {
+            R.fail("状态修改失败!");
+        }
+        return R.ok();
+    }
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssController.java
new file mode 100644
index 0000000..001b8bc
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysOssController.java
@@ -0,0 +1,110 @@
+package com.ruoyi.system.controller.system;
+
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.core.util.ObjectUtil;
+import com.ruoyi.common.core.core.domain.R;
+import com.ruoyi.common.web.core.BaseController;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.orm.core.page.TableDataInfo;
+import com.ruoyi.system.domain.bo.SysOssBo;
+import com.ruoyi.system.domain.vo.SysOssUploadVo;
+import com.ruoyi.system.domain.vo.SysOssVo;
+import com.ruoyi.system.service.ISysOssService;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 文件上传 控制层
+ *
+ * @author Lion Li
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/resource/oss")
+public class SysOssController extends BaseController {
+
+    private final ISysOssService ossService;
+
+    /**
+     * 查询OSS对象存储列表
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOssVo> list(@Validated SysOssBo bo) {
+        return ossService.queryPageList(bo);
+    }
+
+    /**
+     * 查询OSS对象基于id串
+     *
+     * @param ossIds OSS对象ID串
+     */
+    @SaCheckPermission("system:oss:list")
+    @GetMapping("/listByIds/{ossIds}")
+    public R<List<SysOssVo>> listByIds(@NotEmpty(message = "主键不能为空")
+                                       @PathVariable Long[] ossIds) {
+        List<SysOssVo> list = ossService.listSysOssByIds(Arrays.asList(ossIds));
+        return R.ok(list);
+    }
+
+    /**
+     * 上传OSS对象存储
+     *
+     * @param file 文件
+     */
+    @SaCheckPermission("system:oss:upload")
+    @Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<SysOssUploadVo> upload(@RequestPart("file") MultipartFile file) {
+        if (ObjectUtil.isNull(file)) {
+            return R.fail("上传文件不能为空");
+        }
+        SysOssVo oss = ossService.upload(file);
+        SysOssUploadVo uploadVo = new SysOssUploadVo();
+        uploadVo.setUrl(oss.getUrl());
+        uploadVo.setFileName(oss.getOriginalName());
+        uploadVo.setOssId(oss.getOssId().toString());
+        return R.ok(uploadVo);
+    }
+
+    /**
+     * 下载OSS对象
+     *
+     * @param ossId OSS对象ID
+     */
+    @SaCheckPermission("system:oss:download")
+    @GetMapping("/download/{ossId}")
+    public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
+        ossService.download(ossId, response);
+    }
+
+    /**
+     * 删除OSS对象存储
+     *
+     * @param ossIds OSS对象ID串
+     */
+    @SaCheckPermission("system:oss:remove")
+    @Log(title = "OSS对象存储", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ossIds}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ossIds) {
+        Boolean deleted = ossService.deleteWithValidByIds(List.of(ossIds), true);
+        if (!deleted) {
+            R.fail("删除OSS对象存储记录失败!");
+        }
+        return R.ok();
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java
index 07fc85d..4521ce4 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/system/SysProfileController.java
@@ -11,7 +11,9 @@ import com.ruoyi.system.domain.bo.SysUserBo;
 import com.ruoyi.system.domain.bo.SysUserProfileBo;
 import com.ruoyi.system.domain.vo.AvatarVo;
 import com.ruoyi.system.domain.vo.ProfileVo;
+import com.ruoyi.system.domain.vo.SysOssVo;
 import com.ruoyi.system.domain.vo.SysUserVo;
+import com.ruoyi.system.service.ISysOssService;
 import jakarta.annotation.Resource;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -44,6 +46,8 @@ public class SysProfileController extends BaseController
 {
     @Resource
     private ISysUserService userService;
+    @Resource
+    private ISysOssService ossService;
 
     /**
      * 个人信息
@@ -126,13 +130,9 @@ public class SysProfileController extends BaseController
             if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) {
                 return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式");
             }
-
-            //TODO:需要使用OSS来存储操作用户上传的头像
-
-            SysUserVo sysUser = userService.selectUserById(LoginHelper.getUserId());
-            String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);
-            if (userService.updateUserAvatar(sysUser.getUserName(), avatar))
-            {
+            SysOssVo oss = ossService.upload(file);
+            String avatar = oss.getUrl();
+            if (userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId())) {
                 AvatarVo avatarVo = new AvatarVo();
                 avatarVo.setImgUrl(avatar);
                 return R.ok(avatarVo);
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOss.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOss.java
new file mode 100644
index 0000000..e7c1c9b
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOss.java
@@ -0,0 +1,50 @@
+package com.ruoyi.system.domain;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+import com.ruoyi.common.orm.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * OSS对象存储服务对象
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Table("sys_oss")
+public class SysOss extends BaseEntity {
+
+    /**
+     * 对象存储主键
+     */
+    @Id
+    private Long ossId;
+
+    /**
+     * 文件名
+     */
+    private String fileName;
+
+    /**
+     * 原名
+     */
+    private String originalName;
+
+    /**
+     * 文件后缀名
+     */
+    private String fileSuffix;
+
+    /**
+     * URL地址
+     */
+    private String url;
+
+    /**
+     * 服务商
+     */
+    private String service;
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOssConfig.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOssConfig.java
new file mode 100644
index 0000000..24bff59
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysOssConfig.java
@@ -0,0 +1,89 @@
+package com.ruoyi.system.domain;
+
+import com.mybatisflex.annotation.Id;
+import com.mybatisflex.annotation.Table;
+import com.ruoyi.common.orm.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 对象存储配置对象 sys_oss_config
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Table("sys_oss_config")
+public class SysOssConfig extends BaseEntity {
+
+    /**
+     * 主建
+     */
+    @Id
+    private Long ossConfigId;
+
+    /**
+     * 配置key
+     */
+    private String configKey;
+
+    /**
+     * accessKey
+     */
+    private String accessKey;
+
+    /**
+     * 秘钥
+     */
+    private String secretKey;
+
+    /**
+     * 桶名称
+     */
+    private String bucketName;
+
+    /**
+     * 前缀
+     */
+    private String prefix;
+
+    /**
+     * 访问站点
+     */
+    private String endpoint;
+
+    /**
+     * 自定义域名
+     */
+    private String domain;
+
+    /**
+     * 是否https(0否 1是)
+     */
+    private String isHttps;
+
+    /**
+     * 域
+     */
+    private String region;
+
+    /**
+     * 是否默认(0=是,1=否)
+     */
+    private String status;
+
+    /**
+     * 扩展字段
+     */
+    private String ext1;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 桶权限类型(0private 1public 2custom)
+     */
+    private String accessPolicy;
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUser.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUser.java
index 65cc201..dc913b5 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUser.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysUser.java
@@ -1,32 +1,23 @@
 package com.ruoyi.system.domain;
 
-import java.io.Serial;
 import java.util.Date;
 import java.util.List;
 
 import com.mybatisflex.annotation.*;
 import com.ruoyi.common.core.constant.UserConstants;
 import com.ruoyi.common.orm.core.domain.BaseEntity;
-import jakarta.validation.constraints.*;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.ruoyi.common.core.annotation.Excel;
-import com.ruoyi.common.core.annotation.Excel.ColumnType;
-import com.ruoyi.common.core.annotation.Excel.Type;
-import com.ruoyi.common.core.annotation.Excels;
-import com.ruoyi.common.core.xss.Xss;
 
 /**
  * 用户对象 sys_user
  *
  * @author ruoyi
  */
-
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
 @Table(value = "sys_user")
 public class SysUser extends BaseEntity
 {
@@ -66,7 +57,7 @@ public class SysUser extends BaseEntity
     private String gender;
 
     /** 用户头像 */
-    private String avatar;
+    private Long avatar;
 
     /** 密码 */
     private String password;
@@ -100,26 +91,11 @@ public class SysUser extends BaseEntity
      */
     private String remark;
 
-    public SysUser()
-    {
-
-    }
-
     public SysUser(Long userId)
     {
         this.userId = userId;
     }
 
-    public Long getUserId()
-    {
-        return userId;
-    }
-
-    public void setUserId(Long userId)
-    {
-        this.userId = userId;
-    }
-
     public boolean isAdmin()
     {
         return isAdmin(this.userId);
@@ -130,225 +106,6 @@ public class SysUser extends BaseEntity
         return userId != null && 1L == userId;
     }
 
-    public Long getDeptId()
-    {
-        return deptId;
-    }
-
-    public void setDeptId(Long deptId)
-    {
-        this.deptId = deptId;
-    }
-
-    @Xss(message = "用户昵称不能包含脚本字符")
-    @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符")
-    public String getNickName()
-    {
-        return nickName;
-    }
-
-    public void setNickName(String nickName)
-    {
-        this.nickName = nickName;
-    }
-
-    @Xss(message = "用户账号不能包含脚本字符")
-    @NotBlank(message = "用户账号不能为空")
-    @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")
-    public String getUserName()
-    {
-        return userName;
-    }
-
-    public void setUserName(String userName)
-    {
-        this.userName = userName;
-    }
-
-    public String getUserType() {
-        return userType;
-    }
-
-    public void setUserType(String userType) {
-        this.userType = userType;
-    }
-
-    @Email(message = "邮箱格式不正确")
-    @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
-    public String getEmail()
-    {
-        return email;
-    }
-
-    public void setEmail(String email)
-    {
-        this.email = email;
-    }
-
-    @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
-    public String getPhonenumber()
-    {
-        return phonenumber;
-    }
-
-    public void setPhonenumber(String phonenumber)
-    {
-        this.phonenumber = phonenumber;
-    }
-
-    public String getGender()
-    {
-        return gender;
-    }
-
-    public void setGender(String gender)
-    {
-        this.gender = gender;
-    }
-
-    public String getAvatar()
-    {
-        return avatar;
-    }
-
-    public void setAvatar(String avatar)
-    {
-        this.avatar = avatar;
-    }
-
-    @JsonIgnore
-    @JsonProperty
-    public String getPassword()
-    {
-        return password;
-    }
-
-    public void setPassword(String password)
-    {
-        this.password = password;
-    }
-
-    public String getStatus()
-    {
-        return status;
-    }
-
-    public void setStatus(String status)
-    {
-        this.status = status;
-    }
-
-    public String getDelFlag()
-    {
-        return delFlag;
-    }
-
-    public void setDelFlag(String delFlag)
-    {
-        this.delFlag = delFlag;
-    }
-
-    public String getLoginIp()
-    {
-        return loginIp;
-    }
-
-    public void setLoginIp(String loginIp)
-    {
-        this.loginIp = loginIp;
-    }
-
-    public Date getLoginDate()
-    {
-        return loginDate;
-    }
-
-    public void setLoginDate(Date loginDate)
-    {
-        this.loginDate = loginDate;
-    }
-
-    public SysDept getDept()
-    {
-        return dept;
-    }
-
-    public void setDept(SysDept dept)
-    {
-        this.dept = dept;
-    }
-
-    public List<SysRole> getRoles()
-    {
-        return roles;
-    }
-
-    public void setRoles(List<SysRole> roles)
-    {
-        this.roles = roles;
-    }
-
-    public Long[] getRoleIds()
-    {
-        return roleIds;
-    }
-
-    public void setRoleIds(Long[] roleIds)
-    {
-        this.roleIds = roleIds;
-    }
-
-    public Long[] getPostIds()
-    {
-        return postIds;
-    }
-
-    public void setPostIds(Long[] postIds)
-    {
-        this.postIds = postIds;
-    }
-
-    public String getRemark() {
-        return remark;
-    }
-
-    public void setRemark(String remark) {
-        this.remark = remark;
-    }
-
-    public Long getTenantId() {
-        return tenantId;
-    }
-
-    public void setTenantId(Long tenantId) {
-        this.tenantId = tenantId;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
-            .append("userId", getUserId())
-            .append("deptId", getDeptId())
-            .append("userName", getUserName())
-            .append("nickName", getNickName())
-            .append("userType", getUserType())
-            .append("email", getEmail())
-            .append("phonenumber", getPhonenumber())
-            .append("sex", getGender())
-            .append("avatar", getAvatar())
-            .append("password", getPassword())
-            .append("status", getStatus())
-            .append("delFlag", getDelFlag())
-            .append("loginIp", getLoginIp())
-            .append("loginDate", getLoginDate())
-            .append("createBy", getCreateBy())
-            .append("createTime", getCreateTime())
-            .append("updateBy", getUpdateBy())
-            .append("updateTime", getUpdateTime())
-            .append("remark", getRemark())
-            .append("dept", getDept())
-            .toString();
-    }
 
     public boolean isSuperAdmin() {
         return UserConstants.SUPER_ADMIN_ID.equals(this.userId);
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssBo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssBo.java
new file mode 100644
index 0000000..e991400
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssBo.java
@@ -0,0 +1,49 @@
+package com.ruoyi.system.domain.bo;
+
+import com.ruoyi.common.orm.core.domain.BaseEntity;
+import com.ruoyi.system.domain.SysOss;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * OSS对象存储分页查询对象 sys_oss
+ *
+ * @author Lion Li
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysOss.class, reverseConvertGenerate = false)
+public class SysOssBo extends BaseEntity {
+
+    /**
+     * ossId
+     */
+    private Long ossId;
+
+    /**
+     * 文件名
+     */
+    private String fileName;
+
+    /**
+     * 原名
+     */
+    private String originalName;
+
+    /**
+     * 文件后缀名
+     */
+    private String fileSuffix;
+
+    /**
+     * URL地址
+     */
+    private String url;
+
+    /**
+     * 服务商
+     */
+    private String service;
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssConfigBo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssConfigBo.java
new file mode 100644
index 0000000..3795d43
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SysOssConfigBo.java
@@ -0,0 +1,109 @@
+package com.ruoyi.system.domain.bo;
+
+import com.ruoyi.common.core.validate.AddGroup;
+import com.ruoyi.common.core.validate.EditGroup;
+import com.ruoyi.common.orm.core.domain.BaseEntity;
+import com.ruoyi.system.domain.SysOssConfig;
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 对象存储配置业务对象 sys_oss_config
+ *
+ * @author Lion Li
+ * @author 孤舟烟雨
+ * @date 2021-08-13
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SysOssConfig.class, reverseConvertGenerate = false)
+public class SysOssConfigBo extends BaseEntity {
+
+    /**
+     * 主建
+     */
+    @NotNull(message = "主建不能为空", groups = {EditGroup.class})
+    private Long ossConfigId;
+
+    /**
+     * 配置key
+     */
+    @NotBlank(message = "配置key不能为空", groups = {AddGroup.class, EditGroup.class})
+    @Size(min = 2, max = 100, message = "configKey长度必须介于{min}和{max} 之间")
+    private String configKey;
+
+    /**
+     * accessKey
+     */
+    @NotBlank(message = "accessKey不能为空", groups = {AddGroup.class, EditGroup.class})
+    @Size(min = 2, max = 100, message = "accessKey长度必须介于{min}和{max} 之间")
+    private String accessKey;
+
+    /**
+     * 秘钥
+     */
+    @NotBlank(message = "secretKey不能为空", groups = {AddGroup.class, EditGroup.class})
+    @Size(min = 2, max = 100, message = "secretKey长度必须介于{min}和{max} 之间")
+    private String secretKey;
+
+    /**
+     * 桶名称
+     */
+    @NotBlank(message = "桶名称不能为空", groups = {AddGroup.class, EditGroup.class})
+    @Size(min = 2, max = 100, message = "bucketName长度必须介于{min}和{max}之间")
+    private String bucketName;
+
+    /**
+     * 前缀
+     */
+    private String prefix;
+
+    /**
+     * 访问站点
+     */
+    @NotBlank(message = "访问站点不能为空", groups = {AddGroup.class, EditGroup.class})
+    @Size(min = 2, max = 100, message = "endpoint长度必须介于{min}和{max}之间")
+    private String endpoint;
+
+    /**
+     * 自定义域名
+     */
+    private String domain;
+
+    /**
+     * 是否https(Y=是,N=否)
+     */
+    private String isHttps;
+
+    /**
+     * 是否默认(0=是,1=否)
+     */
+    private String status;
+
+    /**
+     * 域
+     */
+    private String region;
+
+    /**
+     * 扩展字段
+     */
+    private String ext1;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 桶权限类型(0private 1public 2custom)
+     */
+    @NotBlank(message = "桶权限类型不能为空", groups = {AddGroup.class, EditGroup.class})
+    private String accessPolicy;
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssConfigVo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssConfigVo.java
new file mode 100644
index 0000000..11ef727
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssConfigVo.java
@@ -0,0 +1,97 @@
+package com.ruoyi.system.domain.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.ruoyi.system.domain.SysOssConfig;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 对象存储配置视图对象 sys_oss_config
+ *
+ * @author Lion Li
+ * @author 孤舟烟雨
+ * @date 2021-08-13
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysOssConfig.class)
+public class SysOssConfigVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主建
+     */
+    private Long ossConfigId;
+
+    /**
+     * 配置key
+     */
+    private String configKey;
+
+    /**
+     * accessKey
+     */
+    private String accessKey;
+
+    /**
+     * 秘钥
+     */
+    private String secretKey;
+
+    /**
+     * 桶名称
+     */
+    private String bucketName;
+
+    /**
+     * 前缀
+     */
+    private String prefix;
+
+    /**
+     * 访问站点
+     */
+    private String endpoint;
+
+    /**
+     * 自定义域名
+     */
+    private String domain;
+
+    /**
+     * 是否https(Y=是,N=否)
+     */
+    private String isHttps;
+
+    /**
+     * 域
+     */
+    private String region;
+
+    /**
+     * 是否默认(0=是,1=否)
+     */
+    private String status;
+
+    /**
+     * 扩展字段
+     */
+    private String ext1;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 桶权限类型(0private 1public 2custom)
+     */
+    private String accessPolicy;
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssUploadVo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssUploadVo.java
new file mode 100644
index 0000000..c7630f0
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssUploadVo.java
@@ -0,0 +1,28 @@
+package com.ruoyi.system.domain.vo;
+
+import lombok.Data;
+
+/**
+ * 上传对象信息
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class SysOssUploadVo {
+
+    /**
+     * URL地址
+     */
+    private String url;
+
+    /**
+     * 文件名
+     */
+    private String fileName;
+
+    /**
+     * 对象存储主键
+     */
+    private String ossId;
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssVo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssVo.java
new file mode 100644
index 0000000..8fe1d37
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysOssVo.java
@@ -0,0 +1,69 @@
+package com.ruoyi.system.domain.vo;
+
+import com.ruoyi.system.domain.SysOss;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * OSS对象存储视图对象 sys_oss
+ *
+ * @author Lion Li
+ */
+@Data
+@AutoMapper(target = SysOss.class)
+public class SysOssVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 对象存储主键
+     */
+    private Long ossId;
+
+    /**
+     * 文件名
+     */
+    private String fileName;
+
+    /**
+     * 原名
+     */
+    private String originalName;
+
+    /**
+     * 文件后缀名
+     */
+    private String fileSuffix;
+
+    /**
+     * URL地址
+     */
+    private String url;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 上传人
+     */
+    private Long createBy;
+
+    /**
+     * 上传人名称
+     */
+    private String createByName;
+
+    /**
+     * 服务商
+     */
+    private String service;
+
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java
index e3ebef8..1f9e961 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SysUserVo.java
@@ -3,6 +3,7 @@ package com.ruoyi.system.domain.vo;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.mybatisflex.annotation.ColumnMask;
+import com.mybatisflex.annotation.RelationOneToOne;
 import com.mybatisflex.core.mask.Masks;
 import com.ruoyi.common.translation.annotation.Translation;
 import com.ruoyi.common.translation.constant.TransConstant;
@@ -78,9 +79,19 @@ public class SysUserVo implements Serializable {
     /**
      * 头像地址
      */
-    @Translation(type = TransConstant.OSS_ID_TO_URL)
     private Long avatar;
 
+    /**
+     * 头像地址URL
+     */
+    @RelationOneToOne(
+        selfField = "avatar",
+        targetTable = "sys_oss",
+        targetField = "ossId",
+        valueField = "url"
+    )
+    private String url;
+
     /**
      * 密码
      */
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssConfigMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssConfigMapper.java
new file mode 100644
index 0000000..181b432
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssConfigMapper.java
@@ -0,0 +1,17 @@
+package com.ruoyi.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ruoyi.system.domain.SysOssConfig;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 对象存储配置Mapper接口
+ *
+ * @author Lion Li
+ * @author 孤舟烟雨
+ * @date 2021-08-13
+ */
+@Mapper
+public interface SysOssConfigMapper extends BaseMapper<SysOssConfig> {
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssMapper.java
new file mode 100644
index 0000000..2880948
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysOssMapper.java
@@ -0,0 +1,14 @@
+package com.ruoyi.system.mapper;
+
+import com.mybatisflex.core.BaseMapper;
+import com.ruoyi.system.domain.SysOss;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 文件上传 数据层
+ *
+ * @author Lion Li
+ */
+@Mapper
+public interface SysOssMapper extends BaseMapper<SysOss> {
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/runner/SystemApplicationRunner.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/runner/SystemApplicationRunner.java
new file mode 100644
index 0000000..e44cf87
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/runner/SystemApplicationRunner.java
@@ -0,0 +1,28 @@
+package com.ruoyi.system.runner;
+
+import com.ruoyi.system.service.ISysOssConfigService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * 初始化 system 模块对应业务数据
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Component
+public class SystemApplicationRunner implements ApplicationRunner {
+
+    private final ISysOssConfigService ossConfigService;
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        ossConfigService.init();
+        log.info("初始化OSS配置成功");
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssConfigService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssConfigService.java
new file mode 100644
index 0000000..09cd538
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssConfigService.java
@@ -0,0 +1,66 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.common.orm.core.page.TableDataInfo;
+import com.ruoyi.common.orm.core.service.IBaseService;
+import com.ruoyi.system.domain.SysOssConfig;
+import com.ruoyi.system.domain.bo.SysOssConfigBo;
+import com.ruoyi.system.domain.vo.SysOssConfigVo;
+
+import java.util.Collection;
+
+/**
+ * 对象存储配置Service接口
+ *
+ * @author Lion Li
+ * @author 孤舟烟雨
+ * @date 2021-08-13
+ */
+public interface ISysOssConfigService extends IBaseService<SysOssConfig> {
+
+    /**
+     * 初始化OSS配置
+     */
+    void init();
+
+    /**
+     * 查询单个
+     */
+    SysOssConfigVo queryById(Long ossConfigId);
+
+    /**
+     * 查询列表
+     */
+    TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo);
+
+
+    /**
+     * 根据新增业务对象插入对象存储配置
+     *
+     * @param bo 对象存储配置新增业务对象
+     * @return
+     */
+    Boolean insertByBo(SysOssConfigBo bo);
+
+    /**
+     * 根据编辑业务对象修改对象存储配置
+     *
+     * @param bo 对象存储配置编辑业务对象
+     * @return
+     */
+    Boolean updateByBo(SysOssConfigBo bo);
+
+    /**
+     * 校验并删除数据
+     *
+     * @param ids     主键集合
+     * @param isValid 是否校验,true-删除前校验,false-不校验
+     * @return
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 转到非默认状态
+     */
+    boolean updateOssConfigStatus(SysOssConfigBo bo);
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssService.java
new file mode 100644
index 0000000..c730764
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssService.java
@@ -0,0 +1,37 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.common.orm.core.page.TableDataInfo;
+import com.ruoyi.common.orm.core.service.IBaseService;
+import com.ruoyi.system.domain.SysOss;
+import com.ruoyi.system.domain.bo.SysOssBo;
+import com.ruoyi.system.domain.vo.SysOssVo;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 文件上传 服务层
+ *
+ * @author Lion Li
+ */
+public interface ISysOssService extends IBaseService<SysOss> {
+
+    TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss);
+
+    List<SysOssVo> listSysOssByIds(Collection<Long> ossIds);
+
+    SysOssVo getById(Long ossId);
+
+    SysOssVo upload(MultipartFile file);
+
+    SysOssVo upload(File file);
+
+    void download(Long ossId, HttpServletResponse response) throws IOException;
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
index 145e612..00a5a74 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java
@@ -195,11 +195,11 @@ public interface ISysUserService extends IBaseService<SysUser>
     /**
      * 修改用户头像
      *
-     * @param userName 用户名
+     * @param userId 用户ID
      * @param avatar 头像地址
-     * @return 结果:true 更新成功,false 更新失败
+     * @return 结果
      */
-    boolean updateUserAvatar(String userName, String avatar);
+    boolean updateUserAvatar(Long userId, Long avatar);
 
     /**
      * 重置用户密码
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssConfigServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssConfigServiceImpl.java
new file mode 100644
index 0000000..7d9ee8c
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssConfigServiceImpl.java
@@ -0,0 +1,192 @@
+package com.ruoyi.system.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.ruoyi.common.orm.core.service.impl.BaseServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.ruoyi.common.core.constant.CacheNames;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.core.utils.MapstructUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.json.utils.JsonUtils;
+import com.ruoyi.common.orm.core.page.PageQuery;
+import com.ruoyi.common.orm.core.page.TableDataInfo;
+import com.ruoyi.common.oss.constant.OssConstant;
+import com.ruoyi.common.redis.utils.CacheUtils;
+import com.ruoyi.common.redis.utils.RedisUtils;
+import com.ruoyi.system.domain.SysOssConfig;
+import com.ruoyi.system.domain.bo.SysOssConfigBo;
+import com.ruoyi.system.domain.vo.SysOssConfigVo;
+import com.ruoyi.system.mapper.SysOssConfigMapper;
+import com.ruoyi.system.service.ISysOssConfigService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+
+import static com.ruoyi.system.domain.table.SysOssConfigTableDef.SYS_OSS_CONFIG;
+
+/**
+ * 对象存储配置Service业务层处理
+ *
+ * @author Lion Li
+ * @author 孤舟烟雨
+ * @author 数据小王子
+ * @date 2023-11-30
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class SysOssConfigServiceImpl extends BaseServiceImpl<SysOssConfigMapper,SysOssConfig> implements ISysOssConfigService {
+
+    private final SysOssConfigMapper baseMapper;
+
+    @Override
+    public QueryWrapper query() {
+        return super.query().from(SYS_OSS_CONFIG);
+    }
+
+    /**
+     * 项目启动时,初始化参数到缓存,加载配置类
+     */
+    @Override
+    public void init() {
+        List<SysOssConfig> list = this.list();
+        // 加载OSS初始化配置
+        for (SysOssConfig config : list) {
+            String configKey = config.getConfigKey();
+            if ("0".equals(config.getStatus())) {
+                RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, configKey);
+            }
+            CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
+        }
+    }
+
+    @Override
+    public SysOssConfigVo queryById(Long ossConfigId) {
+        return this.getOneAs(query().where(SYS_OSS_CONFIG.OSS_CONFIG_ID.eq(ossConfigId)), SysOssConfigVo.class);
+    }
+
+    /**
+     * 根据bo构建QueryWrapper查询条件
+     *
+     * @param bo
+     * @return 查询条件
+     */
+    private QueryWrapper buildQueryWrapper(SysOssConfigBo bo) {
+        QueryWrapper queryWrapper = super.buildBaseQueryWrapper();
+
+        if (StringUtils.isNotEmpty(bo.getConfigKey())) {
+            queryWrapper.and(SYS_OSS_CONFIG.CONFIG_KEY.eq(bo.getConfigKey()));
+        }
+        if (StringUtils.isNotEmpty(bo.getBucketName())) {
+            queryWrapper.and(SYS_OSS_CONFIG.BUCKET_NAME.like(bo.getBucketName()));
+        }
+        if (ObjectUtil.isNotNull(bo.getStatus())) {
+            queryWrapper.and(SYS_OSS_CONFIG.STATUS.eq(bo.getStatus()));
+        }
+        queryWrapper.orderBy(SYS_OSS_CONFIG.OSS_CONFIG_ID.asc());
+
+        return queryWrapper;
+    }
+
+    @Override
+    public TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo) {
+        QueryWrapper queryWrapper = buildQueryWrapper(bo);
+        Page<SysOssConfigVo> page = this.pageAs(PageQuery.build(), queryWrapper, SysOssConfigVo.class);
+        return TableDataInfo.build(page);
+    }
+
+
+    @Override
+    public Boolean insertByBo(SysOssConfigBo bo) {
+        SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class);
+        validEntityBeforeSave(config);
+        boolean flag = this.save(config);
+        if (flag) {
+            // 从数据库查询完整的数据做缓存
+            config = this.getById(config.getOssConfigId());
+            CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
+        }
+        return flag;
+    }
+
+    @Override
+    public Boolean updateByBo(SysOssConfigBo bo) {
+        SysOssConfig config = MapstructUtils.convert(bo, SysOssConfig.class);
+        validEntityBeforeSave(config);
+        boolean flag = this.updateById(config);
+        if (flag) {
+            // 从数据库查询完整的数据做缓存
+            config = this.getById(config.getOssConfigId());
+            CacheUtils.put(CacheNames.SYS_OSS_CONFIG, config.getConfigKey(), JsonUtils.toJsonString(config));
+        }
+        return flag;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(SysOssConfig entity) {
+        if (StringUtils.isNotEmpty(entity.getConfigKey())
+            && !checkConfigKeyUnique(entity)) {
+            throw new ServiceException("操作配置'" + entity.getConfigKey() + "'失败, 配置key已存在!");
+        }
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            if (CollUtil.containsAny(ids, OssConstant.SYSTEM_DATA_IDS)) {
+                throw new ServiceException("系统内置, 不可删除!");
+            }
+        }
+        List<SysOssConfig> list = CollUtil.newArrayList();
+        for (Long configId : ids) {
+            SysOssConfig config = this.getById(configId);
+            list.add(config);
+        }
+        boolean flag = this.removeByIds(ids);
+        if (flag) {
+            list.forEach(sysOssConfig ->
+                CacheUtils.evict(CacheNames.SYS_OSS_CONFIG, sysOssConfig.getConfigKey()));
+        }
+        return flag;
+    }
+
+    /**
+     * 判断configKey是否唯一
+     */
+    private boolean checkConfigKeyUnique(SysOssConfig sysOssConfig) {
+        long ossConfigId = ObjectUtil.isNull(sysOssConfig.getOssConfigId()) ? -1L : sysOssConfig.getOssConfigId();
+
+        QueryWrapper queryWrapper = query();
+        if (StringUtils.isNotEmpty(sysOssConfig.getConfigKey())) {
+            queryWrapper.where(SYS_OSS_CONFIG.CONFIG_KEY.eq(sysOssConfig.getConfigKey()));
+        }
+        SysOssConfig info = this.getOne(queryWrapper);
+        if (ObjectUtil.isNotNull(info) && info.getOssConfigId() != ossConfigId) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 转到非默认状态:停用
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateOssConfigStatus(SysOssConfigBo bo) {
+        SysOssConfig sysOssConfig = MapstructUtils.convert(bo, SysOssConfig.class);
+        boolean updated =  this.updateById(sysOssConfig);
+        if (updated) {
+            RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, sysOssConfig.getConfigKey());
+        }
+        return updated;
+    }
+
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssServiceImpl.java
new file mode 100644
index 0000000..f5ac461
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssServiceImpl.java
@@ -0,0 +1,221 @@
+package com.ruoyi.system.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.mybatisflex.core.paginate.Page;
+import com.mybatisflex.core.query.QueryWrapper;
+import com.ruoyi.common.core.constant.CacheNames;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.core.service.OssService;
+import com.ruoyi.common.core.utils.MapstructUtils;
+import com.ruoyi.common.core.utils.SpringUtils;
+import com.ruoyi.common.core.utils.StreamUtils;
+import com.ruoyi.common.core.utils.StringUtils;
+import com.ruoyi.common.core.utils.file.FileUtils;
+import com.ruoyi.common.orm.core.page.PageQuery;
+import com.ruoyi.common.orm.core.page.TableDataInfo;
+import com.ruoyi.common.orm.core.service.impl.BaseServiceImpl;
+import com.ruoyi.common.oss.core.OssClient;
+import com.ruoyi.common.oss.entity.UploadResult;
+import com.ruoyi.common.oss.enumd.AccessPolicyType;
+import com.ruoyi.common.oss.factory.OssFactory;
+import com.ruoyi.system.domain.SysOss;
+import com.ruoyi.system.domain.bo.SysOssBo;
+import com.ruoyi.system.domain.vo.SysOssVo;
+import com.ruoyi.system.mapper.SysOssMapper;
+import com.ruoyi.system.service.ISysOssService;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+import static com.ruoyi.system.domain.table.SysOssTableDef.SYS_OSS;
+
+/**
+ * 文件上传 服务层实现
+ *
+ * @author Lion Li
+ * @author 数据小王子
+ * @date 2023-11-30
+ */
+@RequiredArgsConstructor
+@Service
+public class SysOssServiceImpl extends BaseServiceImpl<SysOssMapper, SysOss> implements ISysOssService, OssService {
+
+    private final SysOssMapper baseMapper;
+
+    @Override
+    public QueryWrapper query() {
+        return super.query().from(SYS_OSS);
+    }
+
+    private QueryWrapper buildQueryWrapper(SysOssBo bo) {
+        QueryWrapper queryWrapper = super.buildBaseQueryWrapper();
+
+        if (StringUtils.isNotEmpty(bo.getFileName())) {
+            queryWrapper.where(SYS_OSS.FILE_NAME.like(bo.getFileName()));
+        }
+        if (StringUtils.isNotEmpty(bo.getOriginalName())) {
+            queryWrapper.and(SYS_OSS.ORIGINAL_NAME.like(bo.getOriginalName()));
+        }
+        if (StringUtils.isNotEmpty(bo.getFileSuffix())) {
+            queryWrapper.and(SYS_OSS.FILE_SUFFIX.eq(bo.getFileSuffix()));
+        }
+        if (StringUtils.isNotEmpty(bo.getUrl())) {
+            queryWrapper.and(SYS_OSS.URL.eq(bo.getUrl()));
+        }
+        Map<String, Object> params = bo.getParams();
+        if (params.get("beginCreateTime") != null && params.get("endCreateTime") != null) {
+            queryWrapper.and(SYS_OSS.CREATE_TIME.between(params.get("beginCreateTime"), params.get("endCreateTime")));
+        }
+        if (StringUtils.isNotEmpty(bo.getService())) {
+            queryWrapper.and(SYS_OSS.SERVICE.eq(bo.getService()));
+        }
+        queryWrapper.orderBy(SYS_OSS.OSS_ID.asc());
+        return queryWrapper;
+    }
+
+    @Override
+    public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo) {
+        QueryWrapper queryWrapper = buildQueryWrapper(bo);
+        Page<SysOssVo> result = this.pageAs(PageQuery.build(), queryWrapper, SysOssVo.class);
+        List<SysOssVo> filterResult = StreamUtils.toList(result.getRecords(), this::matchingUrl);
+        result.setRecords(filterResult);
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<SysOssVo> listSysOssByIds(Collection<Long> ossIds) {
+        List<SysOssVo> list = new ArrayList<>();
+        for (Long id : ossIds) {
+            SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
+            if (ObjectUtil.isNotNull(vo)) {
+                try {
+                    list.add(this.matchingUrl(vo));
+                } catch (Exception ignored) {
+                    // 如果oss异常无法连接则将数据直接返回
+                    list.add(vo);
+                }
+            }
+        }
+        return list;
+    }
+
+    @Override
+    public String selectUrlByIds(String ossIds) {
+        List<String> list = new ArrayList<>();
+        for (Long id : StringUtils.splitTo(ossIds, Convert::toLong)) {
+            SysOssVo vo = SpringUtils.getAopProxy(this).getById(id);
+            if (ObjectUtil.isNotNull(vo)) {
+                try {
+                    list.add(this.matchingUrl(vo).getUrl());
+                } catch (Exception ignored) {
+                    // 如果oss异常无法连接则将数据直接返回
+                    list.add(vo.getUrl());
+                }
+            }
+        }
+        return String.join(StringUtils.SEPARATOR, list);
+    }
+
+
+
+    @Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
+    @Override
+    public SysOssVo getById(Long ossId) {
+        QueryWrapper queryWrapper=query().where(SYS_OSS.OSS_ID.eq(ossId));
+        return this.getOneAs(queryWrapper,SysOssVo.class);
+    }
+
+    @Override
+    public void download(Long ossId, HttpServletResponse response) throws IOException {
+        SysOssVo sysOss = SpringUtils.getAopProxy(this).getById(ossId);
+        if (ObjectUtil.isNull(sysOss)) {
+            throw new ServiceException("文件数据不存在!");
+        }
+        FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
+        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
+        OssClient storage = OssFactory.instance(sysOss.getService());
+        try(InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) {
+            int available = inputStream.available();
+            IoUtil.copy(inputStream, response.getOutputStream(), available);
+            response.setContentLength(available);
+        } catch (Exception e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    @Override
+    public SysOssVo upload(MultipartFile file) {
+        String originalfileName = file.getOriginalFilename();
+        String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
+        OssClient storage = OssFactory.instance();
+        UploadResult uploadResult;
+        try {
+            uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
+        } catch (IOException e) {
+            throw new ServiceException(e.getMessage());
+        }
+        // 保存文件信息
+        return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult);
+    }
+
+    @Override
+    public SysOssVo upload(File file) {
+        String originalfileName = file.getName();
+        String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
+        OssClient storage = OssFactory.instance();
+        UploadResult uploadResult = storage.uploadSuffix(file, suffix);
+        // 保存文件信息
+        return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult);
+    }
+
+    private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult) {
+        SysOss oss = new SysOss();
+        oss.setUrl(uploadResult.getUrl());
+        oss.setFileSuffix(suffix);
+        oss.setFileName(uploadResult.getFilename());
+        oss.setOriginalName(originalfileName);
+        oss.setService(configKey);
+        this.save(oss);
+        SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
+        return this.matchingUrl(sysOssVo);
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            // 做一些业务上的校验,判断是否需要校验
+        }
+
+        List<SysOss> list = this.listByIds(ids);
+        for (SysOss sysOss : list) {
+            OssClient storage = OssFactory.instance(sysOss.getService());
+            storage.delete(sysOss.getUrl());
+        }
+        return this.removeByIds(ids);
+    }
+
+    /**
+     * 匹配Url
+     *
+     * @param oss OSS对象
+     * @return oss 匹配Url的OSS对象
+     */
+    private SysOssVo matchingUrl(SysOssVo oss) {
+        OssClient storage = OssFactory.instance(oss.getService());
+        // 仅修改桶类型为 private 的URL,临时URL时长为120s
+        if (AccessPolicyType.PRIVATE == storage.getAccessPolicy()) {
+            oss.setUrl(storage.getPrivateUrl(oss.getFileName(), 120));
+        }
+        return oss;
+    }
+}
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
index 7a72e3a..77b90fa 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -301,7 +301,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserMapper, SysUser>
         if (ObjectUtil.isNotNull(userId)) {
             queryWrapper.where(SYS_USER.USER_ID.eq(userId));
         }
-        return this.getOneAs(queryWrapper,SysUserVo.class);
+        return userMapper.selectOneWithRelationsByQueryAs(queryWrapper,SysUserVo.class);//使用Relation注解从sys_oss中查询头像地址URL
     }
 
     /**
@@ -565,15 +565,16 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserMapper, SysUser>
 
     /**
      * 修改用户头像
-     * update sys_user set avatar = #{avatar} where user_name = #{userName}
-     * @param userName 用户名
-     * @param avatar   头像地址
+     * update sys_user set avatar = #{avatar} where user_id = #{userId}
+     * @param userId 用户ID
+     * @param avatar 头像地址
      * @return 结果:true 更新成功,false 更新失败
      */
     @Override
-    public boolean updateUserAvatar(String userName, String avatar) {
-        QueryWrapper queryWrapper = query().where(SYS_USER.USER_NAME.eq(userName));
+    public boolean updateUserAvatar(Long userId, Long avatar) {
+        QueryWrapper queryWrapper = query().where(SYS_USER.USER_ID.eq(userId));
         SysUser sysUser = new SysUser();
+        sysUser.setUserId(userId);
         sysUser.setAvatar(avatar);
         return this.update(sysUser,queryWrapper);
     }
diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml
new file mode 100644
index 0000000..8af22fe
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssConfigMapper.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysOssConfigMapper">
+
+</mapper>
diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml
new file mode 100644
index 0000000..24714f6
--- /dev/null
+++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysOssMapper.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysOssMapper">
+
+</mapper>