Răsfoiți Sursa

Merge branch 'master' into dev-volcengine

cmy 1 lună în urmă
părinte
comite
848e800964
59 a modificat fișierele cu 1091 adăugiri și 197 ștergeri
  1. 15 15
      db/ai_material.sql
  2. 14 8
      db/ai_material_category.sql
  3. 20 0
      db/ai_material_lora.sql
  4. 45 33
      db/ai_material_tag.sql
  5. 1 1
      db/sys_user_info.sql
  6. 10 6
      db/sys_user_role.sql
  7. 7 2
      db/sys_user_role_menu.sql
  8. 4 2
      db/sys_user_role_permission.sql
  9. 20 9
      db/sys_user_role_permission_relation.sql
  10. 2 2
      src/main/java/com/backendsys/modules/material/controller/MaterialCategoryController.java
  11. 1 1
      src/main/java/com/backendsys/modules/material/controller/MaterialController.java
  12. 36 0
      src/main/java/com/backendsys/modules/material/controller/MaterialLoraController.java
  13. 45 0
      src/main/java/com/backendsys/modules/material/controller/MaterialUserController.java
  14. 14 0
      src/main/java/com/backendsys/modules/material/dao/MaterialLoraDao.java
  15. 0 2
      src/main/java/com/backendsys/modules/material/dao/MaterialTagDao.java
  16. 8 4
      src/main/java/com/backendsys/modules/material/entity/Material.java
  17. 23 0
      src/main/java/com/backendsys/modules/material/entity/MaterialAudit.java
  18. 11 0
      src/main/java/com/backendsys/modules/material/entity/MaterialCategory.java
  19. 37 0
      src/main/java/com/backendsys/modules/material/entity/MaterialLora.java
  20. 1 1
      src/main/java/com/backendsys/modules/material/entity/MaterialTag.java
  21. 1 1
      src/main/java/com/backendsys/modules/material/service/MaterialCategoryService.java
  22. 15 0
      src/main/java/com/backendsys/modules/material/service/MaterialLoraService.java
  23. 21 0
      src/main/java/com/backendsys/modules/material/service/MaterialUserService.java
  24. 18 4
      src/main/java/com/backendsys/modules/material/service/impl/MaterialCategoryImpl.java
  25. 38 0
      src/main/java/com/backendsys/modules/material/service/impl/MaterialLoraServiceImpl.java
  26. 27 8
      src/main/java/com/backendsys/modules/material/service/impl/MaterialServiceImpl.java
  27. 14 3
      src/main/java/com/backendsys/modules/material/service/impl/MaterialTagImpl.java
  28. 113 0
      src/main/java/com/backendsys/modules/material/service/impl/MaterialUserServiceImpl.java
  29. 5 1
      src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyuiSocketServiceImpl.java
  30. 4 1
      src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/TencentCosService.java
  31. 85 10
      src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/impl/TencentCosServiceImpl.java
  32. 151 0
      src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/utils/WaterMarkUtil.java
  33. 19 0
      src/main/java/com/backendsys/modules/system/controller/SysUserController.java
  34. 2 0
      src/main/java/com/backendsys/modules/system/dao/SysUserRoleRelationDao.java
  35. 5 0
      src/main/java/com/backendsys/modules/system/entity/SysUserRole.java
  36. 18 0
      src/main/java/com/backendsys/modules/system/entity/SysUserRoleInfo.java
  37. 11 0
      src/main/java/com/backendsys/modules/system/entity/SysUserRoleRelation.java
  38. 3 4
      src/main/java/com/backendsys/modules/system/service/SysUserService.java
  39. 46 19
      src/main/java/com/backendsys/modules/system/service/impl/SysAuthServiceImpl.java
  40. 29 0
      src/main/java/com/backendsys/modules/system/service/impl/SysUserServiceImpl.java
  41. 2 2
      src/main/java/com/backendsys/modules/upload/controller/SysFileController.java
  42. 2 2
      src/main/java/com/backendsys/modules/upload/controller/SysFileMultipartController.java
  43. 1 1
      src/main/java/com/backendsys/modules/upload/service/SysFileMultipartService.java
  44. 1 1
      src/main/java/com/backendsys/modules/upload/service/SysFileService.java
  45. 7 1
      src/main/java/com/backendsys/modules/upload/service/impl/SysFileMultipartServiceImpl.java
  46. 5 6
      src/main/java/com/backendsys/modules/upload/service/impl/SysFileServiceImpl.java
  47. 18 12
      src/main/java/com/backendsys/modules/upload/utils/ObjectKey/ObjectKeyUtil.java
  48. 8 2
      src/main/java/com/backendsys/modules/upload/utils/UploadUtil.java
  49. 1 0
      src/main/resources/application-dev.yml
  50. 1 0
      src/main/resources/application-local.yml
  51. 1 0
      src/main/resources/application-prod.yml
  52. 20 7
      src/main/resources/mapper/ai/material/MaterialCategoryDao.xml
  53. 15 13
      src/main/resources/mapper/ai/material/MaterialDao.xml
  54. 32 0
      src/main/resources/mapper/ai/material/MaterialLoraDao.xml
  55. 1 1
      src/main/resources/mapper/ai/material/MaterialTagDao.xml
  56. 12 11
      src/main/resources/mapper/system/SysUserDao.xml
  57. 1 0
      src/main/resources/mapper/system/SysUserInfoDao.xml
  58. 15 1
      src/main/resources/mapper/system/SysUserRoleDao.xml
  59. 9 0
      src/main/resources/mapper/system/SysUserRoleRelationDao.xml

+ 15 - 15
db/ai_material.sql

@@ -14,8 +14,8 @@ CREATE TABLE `ai_material` (
     `material_name` VARCHAR(100) NOT NULL COMMENT '素材名称',
     `image_thumb_url` VARCHAR(1000) COMMENT '缩略图',
     `image_url` VARCHAR(1000) COMMENT '高清图',
-    `fla_url` VARCHAR(1000) COMMENT 'FLA文件',
-    `psd_url` VARCHAR(1000) COMMENT 'PSD文件',
+    `file_url` VARCHAR(1000) COMMENT '文件地址',
+    `pan_baidu_url` VARCHAR(1000) COMMENT '百度网盘地址',
     `is_copyright` TINYINT DEFAULT '-1' COMMENT '是否有版权 (-1否, 1是)',
     `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
@@ -25,18 +25,18 @@ CREATE TABLE `ai_material` (
     INDEX `idx_material_name` (`material_name`)
 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='素材表';
 
-INSERT INTO ai_material(user_id, category_id, tag_ids, material_name, image_thumb_url, image_url, fla_url, psd_url, is_copyright, create_time) VALUES
-    (1, 1, '1,2', '御姐魔女-1', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:01'),
-    (1, 1, '1,2', '御姐魔女-2', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:02'),
-    (1, 1, '1,2', '御姐魔女-3', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:03'),
-    (1, 1, '1,2', '御姐魔女-4', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:04'),
-    (1, 1, '1,2', '御姐魔女-5', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:05'),
-    (1, 1, '1,2', '御姐魔女-6', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:06'),
-    (1, 1, '1,2', '御姐魔女-7', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:07'),
-    (1, 1, '4,5', '御姐魔女-8', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:08'),
-    (1, 1, '3,4,5', '御姐魔女-9', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:09'),
-    (1, 1, '3', '御姐魔女-10', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:10'),
-    (1, 2, '1,2', '御姐魔女-11', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 'https://www.xxx.com/xx.psd', 1, '2025-07-28 08:30:11'),
-    (1, 1, '1,2', '竹屋内', 'https://cos.daogu.ai/temp/d2361395-6e39-4a01-8c3b-1c79095f4391.png?imageMogr2/thumbnail/315x180/pad/1/color/I2Y4ZjhmOA==', 'https://cos.daogu.ai/temp/d2361395-6e39-4a01-8c3b-1c79095f4391.png', 'https://cos.daogu.ai/temp/2d0e951d-d894-4f88-8904-bf9f1d4797f5.fla', 'https://cos.daogu.ai/temp/aba04bf3-fa42-48d7-923f-b223b7aee22c.psd', 1, '2025-07-30 08:30:00')
+INSERT INTO ai_material(user_id, category_id, tag_ids, material_name, image_thumb_url, image_url, file_url, is_copyright, create_time) VALUES
+    (1, 1, '1,2', '御姐魔女-1', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:01'),
+    (1, 1, '1,2', '御姐魔女-2', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:02'),
+    (1, 1, '1,2', '御姐魔女-3', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:03'),
+    (1, 1, '1,2', '御姐魔女-4', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:04'),
+    (1, 1, '1,2', '御姐魔女-5', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:05'),
+    (1, 1, '1,2', '御姐魔女-6', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:06'),
+    (1, 1, '1,2', '御姐魔女-7', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:07'),
+    (1, 1, '4,5', '御姐魔女-8', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:08'),
+    (1, 1, '3,4,5', '御姐魔女-9', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:09'),
+    (1, 1, '3', '御姐魔女-10', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:10'),
+    (1, 2, '1,2', '御姐魔女-11', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.png', 'https://www.xxx.com/xx.fla', 1, '2025-07-28 08:30:11'),
+    (1, 1, '1,2', '竹屋内', 'https://cos.daogu.ai/temp/d2361395-6e39-4a01-8c3b-1c79095f4391.png?imageMogr2/thumbnail/315x180/pad/1/color/I2Y4ZjhmOA==', 'https://cos.daogu.ai/temp/d2361395-6e39-4a01-8c3b-1c79095f4391.png', 'https://cos.daogu.ai/temp/2d0e951d-d894-4f88-8904-bf9f1d4797f5.fla', 1, '2025-07-30 08:30:00')
 ;
 

+ 14 - 8
db/ai_material_category.sql

@@ -8,18 +8,24 @@ DROP TABLE IF EXISTS `ai_material_category`;
 CREATE TABLE `ai_material_category` (
     PRIMARY KEY (`id`),
     `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
+    `lora_id` BIGINT COMMENT '素材风格ID',
     `category_name` VARCHAR(255) NOT NULL COMMENT '素材分类名称',
+    `is_share` TINYINT DEFAULT '-1' COMMENT '是否共享(-1:不共享, 1:共享)',
     `sort` INT DEFAULT '1' COMMENT '排序',
     INDEX `idx_category_name` (`category_name`)
 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='素材分类表';
 
-INSERT INTO ai_material_category(category_name, sort) VALUES
-    ('人物素材', 7),
-    ('场景素材', 6),
-    ('表情素材', 5),
-    ('兽类素材', 4),
-    ('道具素材', 3),
-    ('特效素材', 2),
-    ('AI素材', 1)
+INSERT INTO ai_material_category(lora_id, is_share, category_name, sort) VALUES
+    (null, 1, '特效素材', 1),
+
+    (1, -1, '人物素材', 7),
+    (1, -1, '场景素材', 6),
+    (1, -1, '异兽素材', 5),
+    (1, -1, '道具素材', 3),
+
+    (2, -1,  '人物素材', 7),
+    (2, -1,  '场景素材', 6),
+    (2, -1,  '异兽素材', 5),
+    (2, -1,  '道具素材', 4)
 ;
 

+ 20 - 0
db/ai_material_lora.sql

@@ -0,0 +1,20 @@
+/**
+Source Server Version: 8.0.31
+Source Database: backendsys
+Date: 2025/07/28 15:57:12
+*/
+
+DROP TABLE IF EXISTS `ai_material_lora`;
+CREATE TABLE `ai_material_lora` (
+    PRIMARY KEY (`id`),
+    `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
+    `lora_name` VARCHAR(255) NOT NULL COMMENT '素材风格名称',
+    `sort` INT DEFAULT '1' COMMENT '排序',
+    INDEX `idx_lora_name` (`lora_name`)
+) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='素材风格表';
+
+INSERT INTO ai_material_lora(lora_name, sort) VALUES
+    ('沙雕素材', 2),
+    ('简笔画素材', 1)
+;
+

+ 45 - 33
db/ai_material_tag.sql

@@ -16,38 +16,50 @@ CREATE TABLE `ai_material_tag` (
 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='素材标签表';
 
 INSERT INTO ai_material_tag(category_id, tag_name, sort) VALUES
-    # 1.人物素材
-    (1, '都市人物', 10),
-    (1, '历史人物', 9),
-    (1, '修仙人物', 8),
-    (1, '灵异人物', 7),
-    (1, '主题人物', 6),
-    (1, '火影人物', 5),
-    (1, '海贼人物', 4),
-    (1, '兽世人物', 3),
-    (1, '精选人物', 2),
-    (1, 'Q版人物', 1),
-    # 2.场景素材
-    (2, '都市场景', 7),
-    (2, '修仙古风', 6),
-    (2, '主题场景', 5),
-    (2, '通用场景', 4),
-    (2, '末日悬疑', 3),
-    (2, '兽世古代', 2),
-    (2, '星际宇宙', 1),
-    # 3.表情素材
-    (3, '身体部件', 2),
-    (3, '人物表情', 1),
-    # 4.兽类素材
-    (4, '异兽素材', 2),
-    (4, '动物素材', 1),
-    # 5.道具素材
-    (5, '通用道具', 3),
-    (5, '交通工具', 2),
-    (5, '武器装备', 1),
-    # 6.特效素材
-    (6, '特效素材', 1),
-    # 7.AI素材
-    (7, 'AI素材', 1)
+    # 1-公用-特效参考
+    (1, '常用特效', 10),
+    (1, '普通特效', 9),
+    (1, '战斗特效', 8),
+    (1, '阵法特效', 7),
+    (1, '全屏特效', 6),
+
+    # 1-沙雕素材-人物素材
+    (2, '精品人物', 10),
+    (2, '修仙人物', 9),
+    (2, '都市人物', 8),
+    (2, '历史人物', 7),
+    # 2-沙雕素材-场景素材
+    (3, '修仙场景', 10),
+    (3, '都市场景', 9),
+    (3, '历史场景', 8),
+    # 3-沙雕素材-异兽素材
+    (4, '异兽', 10),
+    (4, '动物', 9),
+    # 4-沙雕素材-道具素材
+    (5, '修仙道具', 10),
+    (5, '都市道具', 9),
+    (5, '历史道具', 8),
+
+
+    # 6-简笔画素材-人物素材
+    (6, '精品人物', 10),
+    (6, '修仙人物', 9),
+    (6, '都市人物', 8),
+    (6, '历史人物', 7),
+    (6, '末日悬疑人物', 6),
+    # 7-简笔画素材-场景素材
+    (7, '修仙场景', 10),
+    (7, '都市场景', 9),
+    (7, '历史场景', 8),
+    (7, '8090场景', 7),
+    (7, '末日悬疑场景', 6),
+    # 8-简笔画素材-异兽素材
+    (8, '异兽', 10),
+    (8, '动物', 9),
+    # 9-简笔画素材-道具素材
+    (9, '修仙道具', 10),
+    (9, '都市道具', 9),
+    (9, '历史道具', 8)
+
 ;
 

+ 1 - 1
db/sys_user_info.sql

@@ -17,7 +17,7 @@ CREATE TABLE `sys_user_info` (
     `last_login_ip` VARCHAR(20) COMMENT '最后登录IP',
     `last_login_time` DATETIME COMMENT '最后登录时间',
     `is_super` TINYINT(1) DEFAULT '-1' COMMENT '是否超级管理员 (-1否, 1是)',
-    `audit_status` TINYINT(1) DEFAULT '2' COMMENT '账号审核状态 (-1审核拒绝, 1待审核, 2审核通过)',
+    `audit_status` TINYINT(1) DEFAULT '1' COMMENT '账号审核状态 (-1审核拒绝, 1待审核, 2审核通过)',
     `audit_note` VARCHAR(255) COMMENT '账号审核备注',
     `point_balance` FLOAT DEFAULT 0 COMMENT '积分余额',
     `invite_code` VARCHAR(255) COMMENT '邀请码',

+ 10 - 6
db/sys_user_role.sql

@@ -8,20 +8,24 @@ DROP TABLE IF EXISTS `sys_user_role`;
 CREATE TABLE `sys_user_role` (
     PRIMARY KEY (`id`),
     `id` BIGINT AUTO_INCREMENT COMMENT 'ID',
+    `role_sign` VARCHAR(20) COMMENT '角色标志',
     `role_name` VARCHAR(20) NOT NULL COMMENT '角色名称',
     `role_description` VARCHAR(200) COMMENT '角色描述',
+    `login_default_page` VARCHAR(255) DEFAULT '/' COMMENT '默认登录路由',
     `sort` INT DEFAULT '1' COMMENT '排序',
     `status` TINYINT(1) DEFAULT '1' COMMENT '角色状态(1正常 2停用)',
+    UNIQUE KEY (`role_sign`),
     UNIQUE KEY (`role_name`),
     INDEX `idx_nickname` (`role_name`)
 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='系统用户角色表';
 
 # ALTER TABLE sys_user_role_module_relation DROP FOREIGN KEY sys_user_role_module_relation_ibfk_1;
 
-INSERT INTO sys_user_role(role_name, role_description, sort) VALUES
-    ('开发者', '全部权限', 1),
-    ('管理员', '全部功能权限', 2),
-    ('普通用户', '基础权限', 3),
-    ('运营-素材-管理员', '素材管理', 4),
-    ('运营-素材-普通用户', '素材管理', 5)
+INSERT INTO sys_user_role(role_sign, role_name, role_description, login_default_page, sort) VALUES
+    ('SUPERADMIN', '开发者', '全部权限', '/', 1),
+    ('ADMIN', '管理员', '全部功能权限', '/', 2),
+    ('DEFAULT', '普通用户', '基础权限', '/', 3),
+    ('MATERIAL_ADMIN', '素材库-管理员', '素材管理全权限 (允许查看/下载/上传/删除)', '/material/materialList', 4),
+    ('MATERIAL_USER', '素材库-普通用户', '允许查看/下载', '/material/materialList', 5),
+    ('MATERIAL_GUEST', '素材库-游客', '允许查看', '/material/materialList', 6)
 ;

+ 7 - 2
db/sys_user_role_menu.sql

@@ -43,9 +43,14 @@ INSERT INTO sys_user_role_menu(id, parent_id, menu_name, menu_name_en, type, pat
     (11, -1, 'AI成片', 'AI Video', 1, '/ai/generate/video/broadcast/my/broadcast', '', '{}', 'VideoCamera', '34', 909),
 
 
+
     (20, -1, '素材管理', 'Material', 1, '/material', '', '{}', 'MessageBox', '20', 13),
-    (21, 20, '素材列表', 'Material List', 1, '/material/materialList', '/src/views/material/materialList.vue', '{}', null, '20.1', 13),
-#
+    (21, 20, '素材中心', 'Material Center', 1, '/material/materialList', '/src/views/material/materialList.vue', '{}', null, '20.1', 13),
+    (22, 20, '素材用户', 'Material User', 1, '/material/materialUserList', '/src/views/material/materialUserList.vue', '{}', null, '20.4', 12),
+#     (21, 20, '素材列表', 'Material List', 1, '/material/materialList', '/src/views/material/materialList.vue', '{}', null, '20.1', 13),
+#     (21, 20, '沙雕素材', 'Material Sa Diao', 1, '/material/materialList?lora_id=1', '/src/views/material/materialList.vue', '{}', null, '20.1', 13),
+#     (22, 20, '简笔画素材', 'Material Simple', 1, '/material/materialList?lora_id=2', '/src/views/material/materialList.vue', '{}', null, '20.1', 13),
+
 #     (-1, '我的', 'Account', 1, '/account', '', '{}', 'User', '21', 11),
 #     (8, '我的素材', 'My MaterialService', 1, '/account/myMaterial', '', '{}', null, '21.1', 11),
 #     (8, '我的作品', 'My Work', 1, '/account/myWork', '', '{}', null, '21.2', 11),

+ 4 - 2
db/sys_user_role_permission.sql

@@ -84,6 +84,7 @@ INSERT INTO sys_user_role_permission(id, parent_id, permission_name, sort) VALUE
                 ('3.2.3.2', '3.2', '编辑他人用户信息', null),
                 ('3.2.3.3', '3.2', '编辑用户密码', null),
                 ('3.2.3.4', '3.2', '重置用户密码', null),
+                ('3.2.3.5', '3.2', '编辑用户角色绑定', null),
             ('3.2.4', '3.2', '删除用户', null),
             ('3.2.6', '3.2', '审核用户', null),
             ('3.2.7', '3.2', '踢出用户', null),
@@ -197,8 +198,9 @@ INSERT INTO sys_user_role_permission(id, parent_id, permission_name, sort) VALUE
             ('20.1.3', '20.1', '编辑素材', null),
             ('20.1.4', '20.1', '删除素材', null),
         ('20.2', '20', '素材分类列表', null),
-        ('20.3', '20', '素材标签列表', null)
-
+        ('20.3', '20', '素材标签列表', null),
+        ('20.4', '20', '素材用户列表', null),
+            ('20.4.1', '20.4', '审核素材用户', null)
 
 
 #

+ 20 - 9
db/sys_user_role_permission_relation.sql

@@ -26,7 +26,7 @@ INSERT INTO sys_user_role_permission_relation(role_id, permission_id) VALUES
         (1, '3.2'),
             (1, '3.2.1'), (1, '3.2.1.2'), (1, '3.2.2'),
             (1, '3.2.3'),
-                (1, '3.2.3.2'), (1, '3.2.3.3'), (1, '3.2.3.4'),
+                (1, '3.2.3.2'), (1, '3.2.3.3'), (1, '3.2.3.4'), (1, '3.2.3.5'),
             (1, '3.2.4'),
             (1, '3.2.6'),
             (1, '3.2.7'),
@@ -81,6 +81,8 @@ INSERT INTO sys_user_role_permission_relation(role_id, permission_id) VALUES
             (1, '20.1.1'), (1, '20.1.2'), (1, '20.1.3'), (1, '20.1.4'),
         (1, '20.2'),
         (1, '20.3'),
+        (1, '20.4'),
+            (1, '20.4.1'),
 
 #     (1, '21'),
 #         (1, '21.1'),
@@ -169,26 +171,35 @@ INSERT INTO sys_user_role_permission_relation(role_id, permission_id) VALUES
 #     (2, '34'),
 #     (2, '35'),
 
-    (3, '1'),
-        (3, '1.1'),
-            (3, '1.1.2'), (3, '1.1.3'), (3, '1.1.4'), (3, '1.1.6'),
-
     (3, '2'),
-    (3, '31'),
 
     # 运营-素材管理者
     (4, '1.1.2'), (4, '1.1.3'), (4, '1.1.6'),
+#     (4, '3.2.1'),
+    (4, '3.2.3.3'), (4, '3.5.1'),
     (4, '20'),
         (4, '20.1'),
             (4, '20.1.1'), (4, '20.1.2'), (4, '20.1.3'), (4, '20.1.4'),
         (4, '20.2'),
         (4, '20.3'),
+        (4, '20.4'),
+            (4, '20.4.1'),
 
     # 运营-素材使用者
-    (5, '1.1.2'), (5, '1.1.3'), (5, '1.1.6'),
-    (5, '20.1'),
+    (5, '1.1.2'), (5, '1.1.6'),
+#     (5, '3.2.1'),
+    (5, '3.2.3.3'), (5, '3.5.1'),
+    (5, '20'),
         (5, '20.1'),
             (5, '20.1.1'),
         (5, '20.2'),
-        (5, '20.3')
+        (5, '20.3'),
+
+    # 运营-素材游客
+#     (6, '3.2.1'),
+    (6, '3.2.3.3'), (6, '3.5.1'),
+    (6, '20'),
+        (6, '20.1'),
+    (6, '20.2'),
+    (6, '20.3')
 ;

+ 2 - 2
src/main/java/com/backendsys/modules/material/controller/MaterialCategoryController.java

@@ -28,8 +28,8 @@ public class MaterialCategoryController {
     @Operation(summary = "获取素材分类列表(下拉)")
 //    @PreAuthorize("@sr.hasPermission('20.2')")
     @GetMapping("/api/material/getMaterialCategoryPopover")
-    public Result getMaterialCategoryPopover() {
-        return Result.success().put("data", materialCategoryService.selectMaterialCategoryPopover());
+    public Result getMaterialCategoryPopover(MaterialCategory materialCategory) {
+        return Result.success().put("data", materialCategoryService.selectMaterialCategoryPopover(materialCategory));
     }
 
 }

+ 1 - 1
src/main/java/com/backendsys/modules/material/controller/MaterialController.java

@@ -28,7 +28,7 @@ public class MaterialController {
     }
 
     @Operation(summary = "获取素材列表")
-//    @PreAuthorize("@sr.hasPermission('20.1.1')")
+    @PreAuthorize("@sr.hasPermission('20.1.1')")
     @GetMapping("/api/material/getMaterialDetail")
     public Result getMaterialDetail(@Validated(Material.Detail.class) Material material) {
         return Result.success().put("data", materialService.selectMaterialDetail(material));

+ 36 - 0
src/main/java/com/backendsys/modules/material/controller/MaterialLoraController.java

@@ -0,0 +1,36 @@
+package com.backendsys.modules.material.controller;
+
+import com.backendsys.modules.common.utils.Result;
+import com.backendsys.modules.material.entity.MaterialLora;
+import com.backendsys.modules.material.service.MaterialLoraService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Validated
+@RestController
+@Tag(name = "素材风格管理")
+public class MaterialLoraController {
+
+    @Autowired
+    private MaterialLoraService materialLoraService;
+
+    @Operation(summary = "获取素材风格列表")
+//    @PreAuthorize("@sr.hasPermission('20.3')")
+    @GetMapping("/api/material/getMaterialLoraList")
+    public Result getMaterialLoraList(@Validated(MaterialLora.LoraList.class) MaterialLora materialLora) {
+        return Result.success().put("data", materialLoraService.selectMaterialLoraList(materialLora));
+    }
+
+    @Operation(summary = "获取素材风格列表(下拉)")
+//    @PreAuthorize("@sr.hasPermission('20.3')")
+    @GetMapping("/api/material/getMaterialLoraPopover")
+    public Result getMaterialLoraPopover(@Validated(MaterialLora.LoraList.class) MaterialLora materialLora) {
+        return Result.success().put("data", materialLoraService.selectMaterialLoraPopover(materialLora));
+    }
+
+}

+ 45 - 0
src/main/java/com/backendsys/modules/material/controller/MaterialUserController.java

@@ -0,0 +1,45 @@
+package com.backendsys.modules.material.controller;
+
+import com.backendsys.modules.common.aspect.SysLog;
+import com.backendsys.modules.common.utils.Result;
+import com.backendsys.modules.material.entity.MaterialAudit;
+import com.backendsys.modules.material.service.MaterialUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+@Validated
+@RestController
+@Tag(name = "素材用户管理")
+public class MaterialUserController {
+
+    @Autowired
+    private MaterialUserService materialUserService;
+
+    @Operation(summary = "获取素材用户列表")
+    @PreAuthorize("@sr.hasPermission('20.4')")
+    @GetMapping("/api/material/getMaterialUserList")
+    public Result getMaterialUserList(String phone, Long role_id) {
+        return Result.success().put("data", materialUserService.selectMaterialUserList(phone, role_id));
+    }
+
+    @Operation(summary = "获取素材用户角色列表")
+    @PreAuthorize("@sr.hasPermission('20.4')")
+    @GetMapping("/api/material/getMaterialUserRoleList")
+    public Result getMaterialUserRoleList() {
+        return Result.success().put("data", materialUserService.selectMaterialUserRoleList());
+    }
+
+    @SysLog("审核素材用户")
+    @Operation(summary = "审核素材用户")
+    @PreAuthorize("@sr.hasPermission('20.4.1')")
+    @PutMapping("/api/material/auditMaterialUser")
+    public Result auditMaterialUser(@Validated(MaterialAudit.Audit.class) @RequestBody MaterialAudit materialAudit) {
+        return Result.success().put("data", materialUserService.auditMaterialUser(materialAudit));
+    }
+
+
+}

+ 14 - 0
src/main/java/com/backendsys/modules/material/dao/MaterialLoraDao.java

@@ -0,0 +1,14 @@
+package com.backendsys.modules.material.dao;
+
+import com.backendsys.modules.material.entity.MaterialLora;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface MaterialLoraDao extends BaseMapper<MaterialLora> {
+
+    List<MaterialLora> selectMaterialLoraList(MaterialLora materialLora);
+    
+}

+ 0 - 2
src/main/java/com/backendsys/modules/material/dao/MaterialTagDao.java

@@ -11,6 +11,4 @@ public interface MaterialTagDao extends BaseMapper<MaterialTag> {
 
     List<MaterialTag> selectMaterialTagList(MaterialTag materialTag);
 
-
-
 }

+ 8 - 4
src/main/java/com/backendsys/modules/material/entity/Material.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.gson.annotations.JsonAdapter;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
@@ -54,16 +55,19 @@ public class Material {
     private String image_thumb_url;
     @Size(max = 1000, message = "高清图路径长度不超过 {max} 个字符", groups = { Create.class, Update.class })
     private String image_url;
-    @Size(max = 1000, message = "FLA路径长度不超过 {max} 个字符", groups = { Create.class, Update.class })
-    private String fla_url;
-    @Size(max = 1000, message = "PSD路径长度不超过 {max} 个字符", groups = { Create.class, Update.class })
-    private String psd_url;
+    @Size(max = 1000, message = "文件地址长度不超过 {max} 个字符", groups = { Create.class, Update.class })
+    private String file_url;
+    @Size(max = 1000, message = "百度网盘地址长度不超过 {max} 个字符", groups = { Create.class, Update.class })
+    private String pan_baidu_url;
 
     @RangeArray(message="是否有版权,范围应是(-1否, 1是)", value = {"-1", "1"}, groups = { Create.class, Update.class })
     private Integer is_copyright;
 
+    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
     @JsonAdapter(LocalDateTimeAdapter.class)
     private LocalDateTime create_time;
+
+    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
     @JsonAdapter(LocalDateTimeAdapter.class)
     private LocalDateTime update_time;
 

+ 23 - 0
src/main/java/com/backendsys/modules/material/entity/MaterialAudit.java

@@ -0,0 +1,23 @@
+package com.backendsys.modules.material.entity;
+
+import com.backendsys.entity.validator.RangeArray;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+public class MaterialAudit {
+
+    public static interface Audit{}
+
+    @NotNull(message="用户ID不能为空", groups = { Audit.class })
+    private Long user_id;
+    @NotNull(message="审核状态不能为空", groups = { Audit.class })
+    @RangeArray(message="审核状态取值有误,范围应是(-1审核拒绝, 1待审核, 2审核通过)", value = {"-1", "1", "2"}, groups = { Audit.class })
+    private Integer audit_status;
+
+    @RangeArray(message="用户状态取值有误,范围应是(-1禁用, 1启用)", value = {"-1", "1"}, groups = { Audit.class})
+    private Integer status;
+
+    private Long role_id;
+
+}

+ 11 - 0
src/main/java/com/backendsys/modules/material/entity/MaterialCategory.java

@@ -1,5 +1,7 @@
 package com.backendsys.modules.material.entity;
 
+import com.backendsys.entity.validator.RangeArray;
+import com.backendsys.entity.validator.RangeStringArray;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -27,6 +29,15 @@ public class MaterialCategory {
     @NotNull(message="分类ID不能为空", groups = { Detail.class, Update.class, Delete.class })
     private Long category_id;
 
+    @NotNull(message="风格ID不能为空", groups = { CategoryList.class, Detail.class })
+    private Long lora_id;
+
+    @RangeArray(message="是否共享取值有误,范围应是(-1否, 1是)", value = { "-1", "1" }, groups = { Create.class, Update.class })
+    private Integer is_share;
+
+    @TableField(exist = false)
+    private String lora_name;
+
     @Size(max = 100, message = "分类名称长度不超过 {max} 个字符", groups = { Create.class, Update.class })
     @NotEmpty(message="分类名称不能为空", groups = { Create.class, Update.class })
     private String category_name;

+ 37 - 0
src/main/java/com/backendsys/modules/material/entity/MaterialLora.java

@@ -0,0 +1,37 @@
+package com.backendsys.modules.material.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+import org.hibernate.validator.constraints.Range;
+
+@Data
+@TableName("ai_material_lora")
+public class MaterialLora {
+
+    public static interface LoraList{}
+    public static interface Detail{}
+    public static interface Create{}
+    public static interface Update{}
+    public static interface Delete{}
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @TableField("id")
+    @NotNull(message="分类ID不能为空", groups = { Detail.class, Update.class, Delete.class })
+    private Long lora_id;
+
+    @Size(max = 100, message = "分类名称长度不超过 {max} 个字符", groups = { Create.class, Update.class })
+    @NotEmpty(message="分类名称不能为空", groups = { Create.class, Update.class })
+    private String lora_name;
+
+    @Range(min = 1, max = 9999, message = "排序必须在 {min} 到 {max} 之间")
+    private Integer sort;
+
+}

+ 1 - 1
src/main/java/com/backendsys/modules/material/entity/MaterialTag.java

@@ -35,7 +35,7 @@ public class MaterialTag {
     private String tag_name;
 
     @TableField(exist = false)
-    private Integer material_count;
+    private Long material_count;
 
     @Range(min = 1, max = 9999, message = "排序必须在 {min} 到 {max} 之间")
     private Integer sort;

+ 1 - 1
src/main/java/com/backendsys/modules/material/service/MaterialCategoryService.java

@@ -10,6 +10,6 @@ public interface MaterialCategoryService {
     // 获取素材分类列表
     PageEntity selectMaterialCategoryList(MaterialCategory materialCategory);
     // 获取素材分类列表 (下拉)
-    List<MaterialCategory> selectMaterialCategoryPopover();
+    List<MaterialCategory> selectMaterialCategoryPopover(MaterialCategory materialCategory);
 
 }

+ 15 - 0
src/main/java/com/backendsys/modules/material/service/MaterialLoraService.java

@@ -0,0 +1,15 @@
+package com.backendsys.modules.material.service;
+
+import com.backendsys.modules.material.entity.MaterialLora;
+import com.backendsys.utils.response.PageEntity;
+
+import java.util.List;
+
+public interface MaterialLoraService {
+
+    // 获取素材风格列表
+    PageEntity selectMaterialLoraList(MaterialLora materialLora);
+
+    // 获取素材风格列表 (下拉)
+    List<MaterialLora> selectMaterialLoraPopover(MaterialLora materialLora);
+}

+ 21 - 0
src/main/java/com/backendsys/modules/material/service/MaterialUserService.java

@@ -0,0 +1,21 @@
+package com.backendsys.modules.material.service;
+
+import com.backendsys.modules.material.entity.MaterialAudit;
+import com.backendsys.modules.system.entity.SysUserRole;
+import com.backendsys.utils.response.PageEntity;
+
+import java.util.List;
+import java.util.Map;
+
+public interface MaterialUserService {
+
+    // 获取素材用户列表
+    PageEntity selectMaterialUserList(String phone, Long role_id);
+
+    // 获取素材用户角色列表
+    List<SysUserRole> selectMaterialUserRoleList();
+
+    // 审核素材用户
+    Map<String, Object> auditMaterialUser(MaterialAudit materialAudit);
+
+}

+ 18 - 4
src/main/java/com/backendsys/modules/material/service/impl/MaterialCategoryImpl.java

@@ -6,6 +6,7 @@ import com.backendsys.modules.material.service.MaterialCategoryService;
 import com.backendsys.utils.response.PageEntity;
 import com.backendsys.utils.response.PageInfoResult;
 import com.backendsys.utils.v2.PageUtils;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -18,7 +19,7 @@ public class MaterialCategoryImpl implements MaterialCategoryService {
     private MaterialCategoryDao materialCategoryDao;
 
     /**
-     * 获取素材分类列表
+     * 获取素材分类列表 (全部/分页)
      */
     @Override
     public PageEntity selectMaterialCategoryList(MaterialCategory materialCategory) {
@@ -28,11 +29,24 @@ public class MaterialCategoryImpl implements MaterialCategoryService {
     }
 
     /**
-     * 获取素材分类列表 (下拉)
+     * 获取素材分类列表 (下拉)(将 { is_share } 的记录独立出来)
      */
     @Override
-    public List<MaterialCategory> selectMaterialCategoryPopover() {
-        return materialCategoryDao.selectMaterialCategoryList(new MaterialCategory());
+    public List<MaterialCategory> selectMaterialCategoryPopover(MaterialCategory materialCategory) {
+
+        // [DB] 查询非共享的素材分类 { is_share = -1 }
+        materialCategory.setIs_share(-1);
+        List<MaterialCategory> list = materialCategoryDao.selectMaterialCategoryList(materialCategory);
+
+        // [DB] 查询共享的素材分类 { is_share = 1, lora_id = null }
+        materialCategory.setLora_id(null);
+        materialCategory.setIs_share(1);
+        List<MaterialCategory> list_of_share = materialCategoryDao.selectMaterialCategoryList(materialCategory);
+
+        // 将共享分类追加到非共享分类后面
+        list.addAll(list_of_share);
+
+        return list;
     }
 
 }

+ 38 - 0
src/main/java/com/backendsys/modules/material/service/impl/MaterialLoraServiceImpl.java

@@ -0,0 +1,38 @@
+package com.backendsys.modules.material.service.impl;
+
+import com.backendsys.modules.material.dao.MaterialLoraDao;
+import com.backendsys.modules.material.entity.MaterialLora;
+import com.backendsys.modules.material.service.MaterialLoraService;
+import com.backendsys.utils.response.PageEntity;
+import com.backendsys.utils.response.PageInfoResult;
+import com.backendsys.utils.v2.PageUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class MaterialLoraServiceImpl implements MaterialLoraService {
+
+    @Autowired
+    private MaterialLoraDao materialLoraDao;
+
+    /**
+     * 获取素材风格列表
+     */
+    @Override
+    public PageEntity selectMaterialLoraList(MaterialLora materialLora) {
+        PageUtils.startPage();  // 分页
+        List<MaterialLora> list = materialLoraDao.selectMaterialLoraList(materialLora);
+        return new PageInfoResult(list).toEntity();
+    }
+
+    /**
+     * 获取素材风格列表 (下拉)
+     */
+    @Override
+    public List<MaterialLora> selectMaterialLoraPopover(MaterialLora materialLora) {
+        List<MaterialLora> list = materialLoraDao.selectMaterialLoraList(materialLora);
+        return list;
+    }
+}

+ 27 - 8
src/main/java/com/backendsys/modules/material/service/impl/MaterialServiceImpl.java

@@ -24,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
@@ -106,11 +107,21 @@ public class MaterialServiceImpl implements MaterialService {
         MaterialCategory materialCategory = materialCategoryDao.selectById(material.getCategory_id());
         if (materialCategory == null) throw new CustException("素材分类不存在");
 
+        // [Filter] tag_id 过滤掉空值和 0
+        String cleaned = Optional.ofNullable(material.getTag_ids())
+            .orElse("")
+            .replaceAll("\\b0\\b,?", "")
+            .replaceAll(",{2,}", ",")
+            .replaceAll("^,|,$", "");
+        material.setTag_ids(cleaned);
+
         // 生成缩略图,并填充缩略图地址
         if (StrUtil.isNotEmpty(material.getImage_url())) {
             ObjectKeyEntity objectKeyEntity = objectKeyUtil.urlToObjectKey(material.getImage_url());
-            String image_thumb_url = UploadUtil.getImageThumbUrl(material.getImage_url(), objectKeyEntity.getTarget(), 315, 180);
-            material.setImage_thumb_url(image_thumb_url);
+            if (objectKeyEntity != null) {
+                String image_thumb_url = UploadUtil.getImageThumbUrl(material.getImage_url(), objectKeyEntity.getTarget(), 276, 155);
+                material.setImage_thumb_url(image_thumb_url);
+            }
         }
 
         materialDao.insertMaterial(material);
@@ -133,13 +144,23 @@ public class MaterialServiceImpl implements MaterialService {
             if (materialCategory == null) throw new CustException("素材分类不存在");
         }
 
+        // [Filter] tag_id 过滤掉空值和 0
+        String cleaned = Optional.ofNullable(material.getTag_ids())
+            .orElse("")
+            .replaceAll("\\b0\\b,?", "")
+            .replaceAll(",{2,}", ",")
+            .replaceAll("^,|,$", "");
+        material.setTag_ids(cleaned);
+
         // 编辑的时候,如果素材图片有修改,需要删除之前的图片
 
         // 生成缩略图,并填充缩略图地址
         if (StrUtil.isNotEmpty(material.getImage_url())) {
             ObjectKeyEntity objectKeyEntity = objectKeyUtil.urlToObjectKey(material.getImage_url());
-            String image_thumb_url = UploadUtil.getImageThumbUrl(material.getImage_url(), objectKeyEntity.getTarget(), 315, 180);
-            material.setImage_thumb_url(image_thumb_url);
+            if (objectKeyEntity != null) {
+                String image_thumb_url = UploadUtil.getImageThumbUrl(material.getImage_url(), objectKeyEntity.getTarget(), 276, 155);
+                material.setImage_thumb_url(image_thumb_url);
+            }
         }
 
         materialDao.updateMaterial(material);
@@ -158,12 +179,10 @@ public class MaterialServiceImpl implements MaterialService {
 
         // 删除的时候,同时删除对象存储中的素材图片 (异步)
         ObjectKeyEntity image_url_object = objectKeyUtil.urlToObjectKey(detail.getImage_url());
-        ObjectKeyEntity fla_url_object = objectKeyUtil.urlToObjectKey(detail.getFla_url());
-        ObjectKeyEntity psd_url_object = objectKeyUtil.urlToObjectKey(detail.getPsd_url());
+        ObjectKeyEntity file_url_object = objectKeyUtil.urlToObjectKey(detail.getFile_url());
         CompletableFuture.runAsync(() -> {
             sysFileService.deleteObject(image_url_object.getObject_key(), image_url_object.getTarget());
-            sysFileService.deleteObject(fla_url_object.getObject_key(), fla_url_object.getTarget());
-            sysFileService.deleteObject(psd_url_object.getObject_key(), psd_url_object.getTarget());
+            sysFileService.deleteObject(file_url_object.getObject_key(), file_url_object.getTarget());
         });
 
         materialDao.deleteMaterial(material);

+ 14 - 3
src/main/java/com/backendsys/modules/material/service/impl/MaterialTagImpl.java

@@ -1,11 +1,14 @@
 package com.backendsys.modules.material.service.impl;
 
+import com.backendsys.modules.material.dao.MaterialDao;
 import com.backendsys.modules.material.dao.MaterialTagDao;
+import com.backendsys.modules.material.entity.Material;
 import com.backendsys.modules.material.entity.MaterialTag;
 import com.backendsys.modules.material.service.MaterialTagService;
 import com.backendsys.utils.response.PageEntity;
 import com.backendsys.utils.response.PageInfoResult;
 import com.backendsys.utils.v2.PageUtils;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -14,6 +17,9 @@ import java.util.List;
 @Service
 public class MaterialTagImpl implements MaterialTagService {
 
+    @Autowired
+    private MaterialDao materialDao;
+
     @Autowired
     private MaterialTagDao materialTagDao;
 
@@ -34,13 +40,18 @@ public class MaterialTagImpl implements MaterialTagService {
     public List<MaterialTag> selectMaterialTagPopover(MaterialTag materialTag) {
         List<MaterialTag> list = materialTagDao.selectMaterialTagList(materialTag);
 
-        // 筛选出列表中,所有 material_count 的总和
-        Integer all_material_count = list.stream().mapToInt(MaterialTag::getMaterial_count).sum();
+        // [DB] 查询全部素材的数量 (该分类下的)
+        LambdaQueryWrapper<Material> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Material::getCategory_id, materialTag.getCategory_id());
+        Long count = materialDao.selectCount(wrapper);
+
+//        // 筛选出列表中,所有 material_count 的总和
+//        Long count = list.stream().mapToLong(MaterialTag::getMaterial_count).sum();
 
         MaterialTag all = new MaterialTag();
         all.setTag_name("全部");
         all.setTag_id(null);
-        all.setMaterial_count(all_material_count);
+        all.setMaterial_count(count);
         list.add(0, all);
 
         return list;

+ 113 - 0
src/main/java/com/backendsys/modules/material/service/impl/MaterialUserServiceImpl.java

@@ -0,0 +1,113 @@
+package com.backendsys.modules.material.service.impl;
+
+import com.backendsys.exception.CustException;
+import com.backendsys.modules.common.config.security.utils.SecurityUtil;
+import com.backendsys.modules.material.entity.MaterialAudit;
+import com.backendsys.modules.material.service.MaterialUserService;
+import com.backendsys.modules.system.dao.SysUserDao;
+import com.backendsys.modules.system.dao.SysUserInfoDao;
+import com.backendsys.modules.system.dao.SysUserRoleDao;
+import com.backendsys.modules.system.dao.SysUserRoleRelationDao;
+import com.backendsys.modules.system.entity.SysUserDTO;
+import com.backendsys.modules.system.entity.SysUserInfo;
+import com.backendsys.modules.system.entity.SysUserRole;
+import com.backendsys.modules.system.entity.SysUserRoleRelation;
+import com.backendsys.modules.system.service.SysUserService;
+import com.backendsys.utils.response.PageEntity;
+import com.backendsys.utils.response.PageInfoResult;
+import com.backendsys.utils.v2.PageUtils;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class MaterialUserServiceImpl implements MaterialUserService {
+
+    @Autowired
+    private SysUserDao sysUserDao;
+    @Autowired
+    private SysUserInfoDao sysUserInfoDao;
+    @Autowired
+    private SysUserRoleDao sysUserRoleDao;
+    @Autowired
+    private SysUserRoleRelationDao sysUserRoleRelationDao;
+
+    @Autowired
+    private SysUserService sysUserService;
+
+    /**
+     * 获取素材用户列表 ({ invite_code = Material })
+     */
+    @Override
+    public PageEntity selectMaterialUserList(String phone, Long role_id) {
+        PageUtils.startPage();  // 分页
+        SysUserDTO sysUserDTO = new SysUserDTO();
+        sysUserDTO.setPhone(phone);
+        sysUserDTO.setInvite_code("Material");
+        if (role_id != null) {
+            sysUserDTO.setRole_id(Arrays.asList(role_id));
+        }
+        List<SysUserInfo> user_info_list = sysUserInfoDao.selectUserList(sysUserDTO);
+        return new PageInfoResult(user_info_list).toEntity();
+    }
+
+    /**
+     * 获取素材用户角色列表
+     */
+    @Override
+    public List<SysUserRole> selectMaterialUserRoleList() {
+        LambdaQueryWrapper<SysUserRole> wrapper = new LambdaQueryWrapper<>();
+        wrapper.likeRight(SysUserRole::getRole_sign, "MATERIAL");
+        wrapper.orderByAsc(SysUserRole::getSort);
+        List<SysUserRole> list = sysUserRoleDao.selectList(wrapper);
+        return list;
+    }
+
+    /**
+     * 审核素材用户
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> auditMaterialUser(MaterialAudit materialAudit) {
+
+        // 判断是否为素材用户 / 用户是否存在
+        LambdaQueryWrapper<SysUserInfo> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SysUserInfo::getUser_id, materialAudit.getUser_id());
+        wrapper.eq(SysUserInfo::getInvite_code, "Material");
+        SysUserInfo user_info_detail = sysUserInfoDao.selectOne(wrapper);
+        if (user_info_detail == null) throw new CustException("用户不存在");
+
+        // 判断用户是否待审核状态 (-1审核拒绝, 1待审核, 2审核通过)
+        if (materialAudit.getUser_id() == SecurityUtil.getUserId()) throw new CustException("不能审核自己");
+
+        // [DB] 获取素材用户角色ID集合
+        List<SysUserRole> role_list = selectMaterialUserRoleList();
+        List<Long> role_ids = role_list.stream().map(SysUserRole::getRole_id).toList();
+        if (!role_ids.contains(materialAudit.getRole_id())) throw new CustException("必须指定素材用户角色");
+
+        // [DB] 更新用户信息-审核状态/用户状态
+        SysUserInfo entity = new SysUserInfo();
+        entity.setAudit_status(materialAudit.getAudit_status());
+        entity.setStatus(materialAudit.getStatus());
+        sysUserInfoDao.update(entity, wrapper);
+
+        // 如果是禁用状态,则清除该用户的登录信息
+        if (materialAudit.getStatus() == -1) {
+            sysUserService.kickUser(materialAudit.getUser_id());
+        }
+
+        // [DB] 更新用户与角色关系 (先删,后增)
+        LambdaQueryWrapper<SysUserRoleRelation> wrapperRoleRelation = new LambdaQueryWrapper<>();
+        wrapperRoleRelation.eq(SysUserRoleRelation::getUser_id, materialAudit.getUser_id());
+        sysUserRoleRelationDao.delete(wrapperRoleRelation);
+        sysUserRoleRelationDao.insertBatch(materialAudit.getUser_id(), Arrays.asList(materialAudit.getRole_id()));
+
+        return Map.of("user_id", materialAudit.getUser_id());
+    }
+
+}

+ 5 - 1
src/main/java/com/backendsys/modules/sdk/comfyui/service/impl/ComfyuiSocketServiceImpl.java

@@ -80,6 +80,9 @@ public class ComfyuiSocketServiceImpl implements ComfyuiSocketService {
     @Value("${comfyui.token}")
     private String COMFYUI_TOKEN;
 
+    @Value("${comfyui.output}")
+    private String COMFYUI_OUTPUT;
+
     /**
      * 管理多个连接
      */
@@ -346,7 +349,8 @@ public class ComfyuiSocketServiceImpl implements ComfyuiSocketService {
             for (int i = 0; i < images.size(); i++) {
                 JSONObject image = images.getJSONObject(i);
                 String filename = image.getStr("filename");
-                String filepath = "http://o.daogu.ai/" + port + "/" + filename;
+//                String filepath = "http://o.daogu.ai/" + port + "/" + filename;
+                String filepath = "http://" + COMFYUI_OUTPUT + "/" + port + "/" + filename;
                 images_path.add(filepath);
             }
         }

+ 4 - 1
src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/TencentCosService.java

@@ -16,7 +16,10 @@ import java.util.Map;
 public interface TencentCosService {
 
     // [腾讯云COS] 上传对象
-    SysFileResult putObject(MultipartFile multipartFile, String object_key) throws IOException;
+    SysFileResult putObject(MultipartFile multipartFile, String object_key, Integer is_watermark) throws IOException;
+    // [腾讯云COS] 打水印
+    void addWatermask(String object_key);
+
     // [腾讯云COS] 删除对象
     void deleteObject(String object_key);
 

+ 85 - 10
src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/service/impl/TencentCosServiceImpl.java

@@ -22,11 +22,15 @@ import com.qcloud.cos.exception.CosServiceException;
 import com.qcloud.cos.exception.MultiObjectDeleteException;
 import com.qcloud.cos.http.HttpMethodName;
 import com.qcloud.cos.model.*;
+import com.qcloud.cos.model.ciModel.common.ImageProcessRequest;
+import com.qcloud.cos.model.ciModel.persistence.CIUploadResult;
+import com.qcloud.cos.model.ciModel.persistence.PicOperations;
 import com.qcloud.cos.region.Region;
 import com.qcloud.cos.transfer.Transfer;
 import com.qcloud.cos.transfer.TransferManager;
 import com.qcloud.cos.transfer.TransferProgress;
 import com.qcloud.cos.transfer.Upload;
+import com.qcloud.cos.utils.Jackson;
 import org.joda.time.DateTime;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
@@ -36,10 +40,8 @@ import org.springframework.web.multipart.MultipartFile;
 import java.io.*;
 import java.math.BigDecimal;
 import java.net.URL;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
 import java.util.stream.Collectors;
 
 // 图片处理概述
@@ -121,7 +123,7 @@ public class TencentCosServiceImpl implements TencentCosService {
      * - 如果对象是图片,则同时产生缩略图
      */
     @Override
-    public SysFileResult putObject(MultipartFile multipartFile, String object_key) {
+    public SysFileResult putObject(MultipartFile multipartFile, String object_key, Integer is_watermark) {
 
         if (multipartFile.isEmpty()) throw new CustException("file 上传文件不能为空");
 
@@ -155,7 +157,7 @@ public class TencentCosServiceImpl implements TencentCosService {
 //             */
 //            // 判断图片类型
 //            List<String> imageType = Arrays.asList("jpg", "jpeg", "png", "bmp", "webp", "tiff", "gif");
-//            boolean isImage = imageType.contains(FilenameUtil.getFilenameSuffix(multipartFile, false).toLowerCase());
+//            boolean isImage = imageType.contains(CommonUtil.getFilenameSuffix(multipartFile, false).toLowerCase());
 //            if (isImage) {
 //                // 选用样式1,并限定缩略图的宽高最小值为100×100 绝对质量为60
 //                String rule = "imageView2/1/w/100/h/100/q/60";
@@ -172,8 +174,9 @@ public class TencentCosServiceImpl implements TencentCosService {
             inputStream = multipartFile.getInputStream();
             PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, object_key, inputStream, metadata);
 
-            // 普通上传 (没有进度)
-//          //  cosClient.putObject(putObjectRequest);
+//            // 普通上传 (没有进度)
+//            PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
+
 
             // 高级上传
             TransferManager transferManager = TencentCosUtil.createTransferManager(cosClient);
@@ -181,11 +184,23 @@ public class TencentCosServiceImpl implements TencentCosService {
             showTransferProgress(filename, upload, null);    // 查询上传进度,直到上传结束
             UploadResult uploadResult = upload.waitForUploadResult();   // 捕获可能出现的异常
 
+            System.out.println("[腾讯云][cos-uploadResult] = " + uploadResult.getDateStr());
+
+            // 判断是图片类型
+            List<String> imageType = Arrays.asList("jpg", "jpeg", "png", "bmp", "webp", "tiff", "gif");
+            boolean isImage = imageType.contains(CommonUtil.getFilenameSuffix(multipartFile, false).toLowerCase());
+            if (isImage) {
+                // 打水印
+                if (is_watermark != null && is_watermark == 1) {
+                    addWatermask(object_key);
+                }
+            }
+
             // 自定义返回结果实体
             SysFileResult result = new SysFileResult();
             result.setKey(object_key);
-            result.setRequest_id(uploadResult.getRequestId());
-            result.setE_tag(uploadResult.getETag());
+//            result.setRequest_id(uploadResult.getRequestId());
+//            result.setE_tag(uploadResult.getETag());
             result.setDomain(ACCESSIBLE_DOMAIN);
             result.setUrl(ACCESSIBLE_DOMAIN + "/" + object_key);
             result.setTarget(1);
@@ -208,6 +223,66 @@ public class TencentCosServiceImpl implements TencentCosService {
 
     }
 
+    // [腾讯云COS] 打水印
+    public void addWatermask(String object_key) {
+        // 初始化 CosClient
+        COSClient cosClient = getClient();
+        InputStream inputStream = null;
+        try {
+
+            // 对原图发起云上处理
+            ImageProcessRequest request = new ImageProcessRequest(BUCKET_NAME, object_key);
+
+            PicOperations picOperations = new PicOperations();
+            picOperations.setIsPicInfo(1);
+            List<PicOperations.Rule> rule_list = new ArrayList<>();
+
+            PicOperations.Rule rule_item = new PicOperations.Rule();
+
+            // -- 水印 --------------------------------
+            // 文字水印:https://cloud.tencent.com/document/product/436/119425
+            // 上传时处理:https://cloud.tencent.com/document/product/460/18147#.E4.BA.91.E4.B8.8A.E6.95.B0.E6.8D.AE.E5.A4.84.E7.90.86
+            String text = "稻谷文化";
+            byte[] textBates = text.getBytes(StandardCharsets.UTF_8);
+            String encodedText = Base64.getUrlEncoder().encodeToString(textBates).replaceAll("=", "");
+            String rule = "watermark/2/text/" + encodedText +
+                    "/fill/IzNEM0QzRA/fontsize/28/dissolve/50/batch/1/spacing/40/degree/35";
+            // - dissolve: 文字透明度,取值1 - 100 ,默认90
+            // - gravity: 九宫格位置 (gravity/northeast/)
+            // - dx: 水平(横轴)边距,单位为像素,缺省值为0
+            // - dy: 垂直(纵轴)边距,单位为像素,默认值为0
+            // - batch: 平铺水印功能,可将文字水印平铺至整张图片。值为1时,表示开启平铺水印功能
+            // - spacing: 平铺模式下的水平、垂直间距相对文字水印贴图的宽高百分比,范围为[0,100],默认10
+            // - degree: 文字水印的旋转角度设置,取值范围为0 - 360,默认0
+
+
+            rule_item.setBucket(BUCKET_NAME);
+            rule_item.setFileId("/" + object_key);  // 以 '/' 开头是为绝对路径,否则会在当前目录下再创建一个对象路径
+            rule_item.setRule(rule);
+            // ----------------------------------
+
+            rule_list.add(rule_item);
+            picOperations.setRules(rule_list);
+
+            System.out.println("Jackson.toJsonString(picOperations) = " + Jackson.toJsonString(picOperations));
+
+            request.setPicOperations(picOperations);
+            CIUploadResult ciUploadResult = cosClient.processImage(request);
+            System.out.println("[腾讯云][cos-ciUploadResult] OriginalInfo= " + Jackson.toJsonString(ciUploadResult.getOriginalInfo()));
+            System.out.println("[腾讯云][cos-ciUploadResult] ProcessResults = " + Jackson.toJsonString(ciUploadResult.getProcessResults()));
+
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    // 忽略关闭流时的异常
+                }
+            }
+            if (cosClient != null) cosClient.shutdown();
+        }
+    }
+
     // [腾讯云COS] 查询对象是否存在
     @Override
     public boolean doesObjectExist(String object_key) {

+ 151 - 0
src/main/java/com/backendsys/modules/sdk/tencentcloud/cos/utils/WaterMarkUtil.java

@@ -0,0 +1,151 @@
+//package com.backendsys.modules.sdk.tencentcloud.cos.utils;
+//
+//import javax.imageio.ImageIO;
+//import java.awt.*;
+//import java.awt.image.BufferedImage;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.InputStream;
+//import java.io.OutputStream;
+//
+///**
+// * springboot进行图片上传和添加水印
+// * https://cloud.tencent.com/developer/article/2074668
+// */
+//public class WaterMarkUtil {
+//    // 水印透明度
+//    private static float alpha = 0.3f;
+//    // 水印横向位置
+//    private static int positionWidth = 50;
+//    // 水印纵向位置
+//    private static int positionHeight = 100;
+//    // 水印文字字体
+//    private static Font font = new Font("宋体", Font.BOLD, 60);
+//    // 水印文字颜色
+//    private static Color color = Color.red;
+//
+//    /**
+//     * 给图片添加水印文字
+//     *
+//     * @param text       水印文字
+//     * @param srcImgPath 源图片路径
+//     * @param targetPath 目标图片路径
+//     */
+//    public static void markImage(String text, String srcImgPath, String targetPath) {
+//        markImage(text, srcImgPath, targetPath, null);
+//    }
+//
+//    /**
+//     * 给图片添加水印文字、可设置水印文字的旋转角度
+//     *
+//     * @param text 水印文字
+//     * @param srcImgPath 源图片路径
+//     * @param targetPath 目标图片路径
+//     * @param degree 水印旋转
+//     */
+//    public static void markImage(String text, String srcImgPath, String targetPath, Integer degree) {
+//
+//        OutputStream os = null;
+//        try {
+//            // 0、图片类型
+//            String type = srcImgPath.substring(srcImgPath.indexOf(".") + 1, srcImgPath.length());
+//
+//            // 1、源图片
+//            Image srcImg = ImageIO.read(new File(srcImgPath));
+//
+//            int imgWidth = srcImg.getWidth(null);
+//            int imgHeight = srcImg.getHeight(null);
+//
+//            BufferedImage buffImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
+//
+//            // 2、得到画笔对象
+//            Graphics2D g = buffImg.createGraphics();
+//            // 3、设置对线段的锯齿状边缘处理
+//            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+//            g.drawImage(srcImg.getScaledInstance(imgWidth, imgHeight, Image.SCALE_SMOOTH), 0, 0, null);
+//            // 4、设置水印旋转
+//            if (null != degree) {
+//                g.rotate(Math.toRadians(degree), (double) buffImg.getWidth() / 2, (double) buffImg.getHeight() / 2);
+//            }
+//            // 5、设置水印文字颜色
+//            g.setColor(color);
+//            // 6、设置水印文字Font
+//            g.setFont(font);
+//            // 7、设置水印文字透明度
+//            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+//            // 8、第一参数->设置的内容,后面两个参数->文字在图片上的坐标位置(x,y)
+//
+//            //设置水印的坐标
+//            int x = imgWidth - 2*getWatermarkLength(text, g);
+//            int y = imgHeight - 2*getWatermarkLength(text, g);
+//            g.drawString(text, x, y);  //画出水印
+////            g.drawString(text, positionWidth, positionHeight);
+//            // 9、释放资源
+//            g.dispose();
+//            // 10、生成图片
+//            os = new FileOutputStream(targetPath);
+//            // ImageIO.write(buffImg, "JPG", os);
+//            ImageIO.write(buffImg, type.toUpperCase(), os);
+//
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        } finally {
+//            try {
+//                if (null != os)
+//                    os.close();
+//            } catch (Exception e) {
+//                e.printStackTrace();
+//            }
+//        }
+//    }
+//
+//    /**
+//     * 给图片添加水印文字、可设置水印文字的旋转角度
+//     * @param text 水印文字
+//     * @param inputStream 源图片路径
+//     * @param outputStream 目标图片路径
+//     * @param degree 水印旋转
+//     * @param typeName
+//     */
+//    public static void markImageByIO(String text, InputStream inputStream, OutputStream outputStream,
+//                                     Integer degree, String typeName) {
+//        try {
+//            // 1、源图片
+//            Image srcImg = ImageIO.read(inputStream);
+//
+//            int imgWidth = srcImg.getWidth(null);
+//            int imgHeight = srcImg.getHeight(null);
+//            BufferedImage buffImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
+//
+//            // 2、得到画笔对象
+//            Graphics2D g = buffImg.createGraphics();
+//            // 3、设置对线段的锯齿状边缘处理
+//            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+//            g.drawImage(srcImg.getScaledInstance(imgWidth, imgHeight, Image.SCALE_SMOOTH), 0, 0, null);
+//            // 4、设置水印旋转
+//            if (null != degree) {
+//                g.rotate(Math.toRadians(degree), (double) buffImg.getWidth() / 2, (double) buffImg.getHeight() / 2);
+//            }
+//            // 5、设置水印文字颜色
+//            g.setColor(color);
+//            // 6、设置水印文字Font
+//            g.setFont(font);
+//            // 7、设置水印文字透明度
+//            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+//            // 8、第一参数->设置的内容,后面两个参数->文字在图片上的坐标位置(x,y)
+//
+//            g.drawString(text, positionWidth, positionHeight);
+//            // 9、释放资源
+//            g.dispose();
+//            // 10、生成图片
+//            ImageIO.write(buffImg, typeName.toUpperCase(), outputStream);
+//
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        }
+//    }
+//
+//    public static int getWatermarkLength(String waterMarkContent, Graphics2D g) {
+//        return g.getFontMetrics(g.getFont()).charsWidth(waterMarkContent.toCharArray(), 0, waterMarkContent.length());
+//    }
+//}

+ 19 - 0
src/main/java/com/backendsys/modules/system/controller/SysUserController.java

@@ -7,6 +7,7 @@ import com.backendsys.modules.common.config.security.enums.SecurityEnum;
 import com.backendsys.modules.common.config.security.utils.SecurityUtil;
 import com.backendsys.modules.common.utils.Result;
 import com.backendsys.modules.system.entity.SysUserDTO;
+import com.backendsys.modules.system.entity.SysUserRoleRelation;
 import com.backendsys.modules.system.service.SysUserService;
 import com.backendsys.utils.response.PageEntity;
 import io.swagger.v3.oas.annotations.Operation;
@@ -112,6 +113,24 @@ public class SysUserController {
         return Result.success().put("data", sysUserService.updateUserInfo(sysUserDTO));
     }
 
+    @SysLog("编辑系统用户角色绑定")
+    @Operation(summary = "编辑系统用户角色绑定")
+    @PreAuthorize("@sr.hasPermission('3.2.3.5')")
+    @PutMapping("/api/system/user/updateUserRoleInfo")
+    public Result updateUserRoleInfo(@Validated(SysUserRoleRelation.Update.class) @RequestBody SysUserRoleRelation sysUserRoleRelation) {
+
+        // - 不传 user_id 时,修改目标为 当前用户
+        Long user_id = sysUserRoleRelation.getUser_id();
+        if (ObjectUtil.isEmpty(user_id)) {
+            user_id = SecurityUtil.getUserId();
+            sysUserRoleRelation.setUser_id(user_id);
+        } else if (!SecurityUtil.getUserId().equals(1L) && user_id.equals(1L)) {
+            throw new CustException("不能编辑超管账号");
+        }
+
+        return Result.success().put("data", sysUserService.updateUserRoleRelation(sysUserRoleRelation));
+    }
+
     @SysLog("编辑系统用户密码")
     @Operation(summary = "编辑系统用户密码")
     @PreAuthorize("@sr.hasPermission('3.2.3.3')")

+ 2 - 0
src/main/java/com/backendsys/modules/system/dao/SysUserRoleRelationDao.java

@@ -11,4 +11,6 @@ public interface SysUserRoleRelationDao extends BaseMapper<SysUserRoleRelation>
 
     List<Long> selectUserRoleIds(Long user_id);
 
+    int insertBatch(Long user_id, List<Long> role_ids);
+
 }

+ 5 - 0
src/main/java/com/backendsys/modules/system/entity/SysUserRole.java

@@ -28,12 +28,17 @@ public class SysUserRole {
     @TableField("id")
     private Long role_id;
 
+    private String role_sign;
+
     @Size(min = 2, max = 50, message = "角色名称长度在 {min}-{max} 字符", groups = { Create.class, Update.class })
     private String role_name;
 
     @Size(max = 200, message = "角色描述长度不超过 {max} 字符", groups = { Create.class, Update.class })
     private String role_description;
 
+    @Size(max = 255, message = "默认登录路由长度不超过 {max} 字符", groups = { Create.class, Update.class })
+    private String login_default_page;
+
     @TableField(exist = false)
     private Integer user_count;
 

+ 18 - 0
src/main/java/com/backendsys/modules/system/entity/SysUserRoleInfo.java

@@ -0,0 +1,18 @@
+package com.backendsys.modules.system.entity;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class SysUserRoleInfo {
+
+    public static interface Update{}
+
+    @NotNull(message="role_ids 不能为空", groups = { Update.class })
+    private List<Long> role_ids;
+
+    private Long user_id;
+
+}

+ 11 - 0
src/main/java/com/backendsys/modules/system/entity/SysUserRoleRelation.java

@@ -1,15 +1,26 @@
 package com.backendsys.modules.system.entity;
 
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
+import java.util.List;
+
 @Data
 @TableName("sys_user_role_relation")
 public class SysUserRoleRelation {
+
+    public static interface Update{}
+
     @TableId(type = IdType.AUTO)
     private Long id;
     private Long user_id;
     private Long role_id;
+
+    @TableField(exist = false)
+    @NotNull(message="role_ids 不能为空", groups = { Update.class })
+    private List<Long> role_ids;
 }

+ 3 - 4
src/main/java/com/backendsys/modules/system/service/SysUserService.java

@@ -1,9 +1,6 @@
 package com.backendsys.modules.system.service;
 
-import com.backendsys.modules.system.entity.SysUser;
-import com.backendsys.modules.system.entity.SysUserDTO;
-import com.backendsys.modules.system.entity.SysUserInfo;
-import com.backendsys.modules.system.entity.SysUserInfoSimple;
+import com.backendsys.modules.system.entity.*;
 import com.backendsys.utils.response.PageEntity;
 import com.baomidou.mybatisplus.extension.service.IService;
 
@@ -29,6 +26,8 @@ public interface SysUserService extends IService<SysUser> {
     Map<String, Object> insertUser(SysUserDTO sysUserDTO);
     // 编辑系统用户信息
     Map<String, Object> updateUserInfo(SysUserDTO sysUserDTO);
+    // 编辑系统用户角色绑定
+    Map<String, Object> updateUserRoleRelation(SysUserRoleRelation sysUserRoleRelation);
     // 编辑系统用户密码
     Map<String, Object> updateUserPassword(SysUserDTO sysUserDTO);
     // 重置系统用户密码

+ 46 - 19
src/main/java/com/backendsys/modules/system/service/impl/SysAuthServiceImpl.java

@@ -3,6 +3,7 @@ package com.backendsys.modules.system.service.impl;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.date.DateUnit;
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.backendsys.exception.CustException;
@@ -12,6 +13,7 @@ import com.backendsys.modules.common.config.security.utils.*;
 import com.backendsys.modules.system.dao.SysMobileAreaDao;
 import com.backendsys.modules.system.dao.SysUserDao;
 import com.backendsys.modules.system.dao.SysUserInfoDao;
+import com.backendsys.modules.system.dao.SysUserRoleDao;
 import com.backendsys.modules.system.entity.*;
 import com.backendsys.modules.system.service.SysAuthService;
 import com.backendsys.modules.system.service.SysCommonService;
@@ -24,6 +26,7 @@ import jakarta.servlet.ServletOutputStream;
 import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.env.Environment;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -38,6 +41,8 @@ import java.util.concurrent.TimeUnit;
 @Service
 public class SysAuthServiceImpl implements SysAuthService {
 
+    @Autowired
+    private Environment env;
     @Autowired
     private JwtUtil jwtUtil;
     @Autowired
@@ -56,6 +61,8 @@ public class SysAuthServiceImpl implements SysAuthService {
     @Autowired
     private SysUserDao sysUserDao;
     @Autowired
+    private SysUserRoleDao sysUserRoleDao;
+    @Autowired
     private SysUserInfoDao sysUserInfoDao;
     @Autowired
     private SysUserService sysUserService;
@@ -151,7 +158,7 @@ public class SysAuthServiceImpl implements SysAuthService {
 
         // 判断用户是否审核
         Integer audit_status = sysUserInfo.getAudit_status();
-        if (audit_status != null && audit_status.equals(1)) throw new CustException("用户审核中");
+        if (audit_status != null && audit_status.equals(1)) throw new CustException("用户正在审核中");
         if (audit_status != null && audit_status.equals(-1)) throw new CustException("用户审核未通过,请与客服联系");
 
         // 判断用户是否启用
@@ -314,13 +321,16 @@ public class SysAuthServiceImpl implements SysAuthService {
         Integer phoneAreaCode = sysUserDTO.getPhone_area_code();
         Integer phoneValidCode = sysUserDTO.getPhone_valid_code();
 
-        // 判断是否处于登录错误的冻结状态 (2分钟内错误5次,则出现冻结提示)
-        lockStatusUtil.checkLockStatus(APPLICATION_NAME + "-register-error", username);
-        lockStatusUtil.checkLockStatus(APPLICATION_NAME + "-register-error", phone);
-        // 判断图形验证码是否正确
-        if (!captchaUtil.isCaptchaValid(captcha, httpRequestUtil.getKaptchaKey())) {
-            loginFail("验证码错误", username, false);
-            return null;
+        String activeProfile = env.getActiveProfiles()[0];
+        if (!"local".equals(activeProfile)) {
+            // 判断是否处于登录错误的冻结状态 (2分钟内错误5次,则出现冻结提示)
+            lockStatusUtil.checkLockStatus(APPLICATION_NAME + "-register-error", username);
+            lockStatusUtil.checkLockStatus(APPLICATION_NAME + "-register-error", phone);
+            // 判断图形验证码是否正确
+            if (!captchaUtil.isCaptchaValid(captcha, httpRequestUtil.getKaptchaKey())) {
+                loginFail("验证码错误", username, false);
+                return null;
+            }
         }
 
         // [查询] 判断用户名是否存在
@@ -328,12 +338,14 @@ public class SysAuthServiceImpl implements SysAuthService {
         if (sysUser1 != null) throw new CustException("用户名 (" + username + ") 已被注册");
         
         // 判断短信验证码是否正确
-        String redisKey = APPLICATION_NAME + "-sms-register" + "-" + phone;
-        Integer smsCode = redisUtil.getCacheObject(redisKey);
-        // 判断是否发送验证码
-        if ("false".equals(SMS_DEBUG) && smsCode == null) throw new CustException("请先发送短信验证码");
-        // 判断短信验证码是否错误
-        if ("false".equals(SMS_DEBUG) && !smsCode.equals(phoneValidCode)) loginFail("短信验证码错误", phone, true);
+        if (!"local".equals(activeProfile)) {
+            String redisKey = APPLICATION_NAME + "-sms-register" + "-" + phone;
+            Integer smsCode = redisUtil.getCacheObject(redisKey);
+            // 判断是否发送验证码
+            if ("false".equals(SMS_DEBUG) && smsCode == null) throw new CustException("请先发送短信验证码");
+            // 判断短信验证码是否错误
+            if ("false".equals(SMS_DEBUG) && !smsCode.equals(phoneValidCode)) loginFail("短信验证码错误", phone, true);
+        }
 
 
         // [查询] 判断手机号是否存在
@@ -359,14 +371,29 @@ public class SysAuthServiceImpl implements SysAuthService {
 
         // 做成后台可控制?
 
-        // 注册时,默认使用 权限
-        registerEntity.setRole_id(Arrays.asList(3L));
 
+        // 邀请码
+        registerEntity.setInvite_code(sysUserDTO.getInvite_code());
 
+        // 注册时,默认使用 权限 (DEFAULT)
+        String role_sign = "DEFAULT";
 
-        registerEntity.setInvite_code(sysUserDTO.getInvite_code());
-        // 注册时,状态为禁用
-        registerEntity.setStatus(-1);
+        // 如果邀请码是 (Material),则注册成为 [素材游客]
+        if ("Material".equals(sysUserDTO.getInvite_code())) role_sign = "MATERIAL_GUEST";
+
+        LambdaQueryWrapper<SysUserRole> wrapperRole = new LambdaQueryWrapper<>();
+        wrapperRole.eq(SysUserRole::getRole_sign, role_sign);
+        SysUserRole roleDetail = sysUserRoleDao.selectOne(wrapperRole);
+        registerEntity.setRole_id(Arrays.asList(roleDetail.getRole_id()));
+
+        // 注册时,审核状态为 待审核 (-1拒绝, 1待审核, 2审核通过)
+        registerEntity.setAudit_status(1);
+
+        // 注册时,状态为 禁用
+        // registerEntity.setStatus(-1);
+
+        // 随机昵称 (6位)
+        registerEntity.setNickname("用户" + RandomUtil.randomStringUpper(6));
 
         // 创建用户
         sysUserDao.insertUser(registerEntity);

+ 29 - 0
src/main/java/com/backendsys/modules/system/service/impl/SysUserServiceImpl.java

@@ -289,6 +289,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserDao, SysUser> impleme
                 sysUserDTO.setLast_login_uuid("");
             }
 
+            System.out.println("sysUserDTO = " + sysUserDTO);
+
             sysUserDao.updateUserInfo(sysUserDTO);
 
             return Map.of("user_id", sysUserDTO.getUser_id());
@@ -297,6 +299,33 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserDao, SysUser> impleme
         } finally { lock.unlock(); }
     }
 
+
+    /**
+     * 编辑系统用户角色绑定
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> updateUserRoleRelation(SysUserRoleRelation sysUserRoleRelation) {
+        RLock lock = redissonClient.getLock("updateUserRoleRelation");
+        try { lock.tryLock(3, TimeUnit.SECONDS);
+
+            List<Long> role_ids = sysUserRoleRelation.getRole_ids();
+            Long user_id = sysUserRoleRelation.getUser_id();
+
+            // 1.删除全部用户与角色的关系 (sys_user_role_relation)
+            LambdaQueryWrapper<SysUserRoleRelation> wrapperRoleRelation = new LambdaQueryWrapper<>();
+            wrapperRoleRelation.eq(SysUserRoleRelation::getUser_id, user_id);
+            sysUserRoleRelationDao.delete(wrapperRoleRelation);
+
+            // 2.重新添加参数中的角色与权限的关系 (sys_user_role_relation)
+            sysUserRoleRelationDao.insertBatch(user_id, role_ids);
+
+            return Map.of("user_id", sysUserRoleRelation.getUser_id());
+
+        } catch (InterruptedException e) { throw new RuntimeException(e);
+        } finally { lock.unlock(); }
+    }
+
     /**
      * 编辑系统用户密码
      */

+ 2 - 2
src/main/java/com/backendsys/modules/upload/controller/SysFileController.java

@@ -66,8 +66,8 @@ public class SysFileController {
     @Operation(summary = "上传文件 (普通上传,单文件上传不超过 100MB)")
     @PreAuthorize("@sr.hasPermission('1.1.3')")
     @PostMapping("/api/upload/uploadSmall")
-    public Result uploadSmall(@RequestParam("file") MultipartFile multipartFile, Long category_id) {
-        return Result.success().put("data", sysFileService.uploadSmall(multipartFile, category_id));
+    public Result uploadSmall(@RequestParam("file") MultipartFile multipartFile, Long category_id, Integer is_watermark) {
+        return Result.success().put("data", sysFileService.uploadSmall(multipartFile, category_id, is_watermark));
     }
 
     @SysLog("编辑文件")

+ 2 - 2
src/main/java/com/backendsys/modules/upload/controller/SysFileMultipartController.java

@@ -50,8 +50,8 @@ public class SysFileMultipartController {
     @Operation(summary = "合并分块")
     @PreAuthorize("@sr.hasPermission('1.1.3')")
     @PostMapping("/api/upload/multipartUploadComplete")
-    public Result multipartUploadComplete(String upload_id) {
-        return Result.success().put("data", sysFileMultipartService.multipartUploadComplete(upload_id));
+    public Result multipartUploadComplete(String upload_id, Integer is_watermark) {
+        return Result.success().put("data", sysFileMultipartService.multipartUploadComplete(upload_id, is_watermark));
     }
 
     @Operation(summary = "查询分块上传情况")

+ 1 - 1
src/main/java/com/backendsys/modules/upload/service/SysFileMultipartService.java

@@ -14,7 +14,7 @@ public interface SysFileMultipartService {
     // 2.上传分块
     Map<String, Object> multipartUpload(MultipartFile multipartFile, String upload_id, Integer upload_chunk_index);
     // 3.完成分块上传
-    SysFile multipartUploadComplete(String upload_id);
+    SysFile multipartUploadComplete(String upload_id, Integer is_watermark);
 
     // 查询分块上传情况
     Map<String, Object> listParts(String upload_id, String object_key);

+ 1 - 1
src/main/java/com/backendsys/modules/upload/service/SysFileService.java

@@ -18,7 +18,7 @@ public interface SysFileService extends IService<SysFile> {
     Map<String, Object> selectUploadTarget();
 
     // 上传文件 (单文件大小不超过 n)
-    SysFile uploadSmall(MultipartFile file, Long category_id);
+    SysFile uploadSmall(MultipartFile file, Long category_id, Integer is_watermark);
 
     // 删除文件
     Map<String, Object> removeUploadFile(SysFile sysFile, SysFile querySysFile);

+ 7 - 1
src/main/java/com/backendsys/modules/upload/service/impl/SysFileMultipartServiceImpl.java

@@ -303,7 +303,7 @@ public class SysFileMultipartServiceImpl implements SysFileMultipartService {
 
     // 完成分块上传
     @Override
-    public SysFile multipartUploadComplete(String upload_id) {
+    public SysFile multipartUploadComplete(String upload_id, Integer is_watermark) {
 
         if (StrUtil.isEmpty(upload_id)) throw new CustException("upload_id 不能为空");
 
@@ -349,6 +349,12 @@ public class SysFileMultipartServiceImpl implements SysFileMultipartService {
                 // [腾讯云] 合并分块
                 CompleteMultipartUploadResult completeResult = tencentCosService.completeMultipartUpload(upload_id, sysFileEntity.getObject_key(), etags);
                 if (completeResult == null) throw new CustException("分块合并失败");
+                
+                if (is_watermark != null && is_watermark == 1) {
+                    // [腾讯云] 添加水印
+                    String object_key = completeResult.getKey();
+                    tencentCosService.addWatermask(object_key);
+                }
 
                 // 拼接图片路径
                 sysFileEntity.setUrl(TENCENT_ACCESSIBLE_DOMAIN + "/" + completeResult.getKey());

+ 5 - 6
src/main/java/com/backendsys/modules/upload/service/impl/SysFileServiceImpl.java

@@ -88,7 +88,6 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileDao, SysFile> impleme
                     // 腾讯云 (color值通过base64加密, #f8f8f8)
                     if (sysFile.getTarget() == 1) {
                         backgroundColor = "#" + backgroundColor;
-                        System.out.println("base64 encode: " + Base64.encode(backgroundColor));
                         sysFile.setUrl_thumb(sysFile.getUrl() + "?imageMogr2/thumbnail/" + width + "x" + height + "/pad/1/color/" + Base64.encode(backgroundColor));
                     }
                     // 抖音云
@@ -142,7 +141,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileDao, SysFile> impleme
     }
 
     // [方法] 上传事件
-    private SysFile uploadEvent(MultipartFile multipartFile, Long category_id, Integer target) {
+    private SysFile uploadEvent(MultipartFile multipartFile, Long category_id, Integer is_watermark, Integer target) {
         try {
 
             String filename = multipartFile.getOriginalFilename();
@@ -156,7 +155,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileDao, SysFile> impleme
             }
             if (target == 1) {
                 // [腾讯云-上传对象]
-                uploadResult = tencentCosService.putObject(multipartFile, null);
+                uploadResult = tencentCosService.putObject(multipartFile, null, is_watermark);
             }
             if (target == 3) {
                 // [抖音云-上传对象]
@@ -242,7 +241,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileDao, SysFile> impleme
      * - target: 上传目标 (-1:本地, 1:腾讯云, 2:阿里云, 3.抖音云)
      */
     @Override
-    public SysFile uploadSmall(MultipartFile multipartFile, Long category_id) {
+    public SysFile uploadSmall(MultipartFile multipartFile, Long category_id, Integer is_watermark) {
 
         List<SysCommon> sysCommonList = sysCommonService.getCommonByCategory("UPLOAD");
 
@@ -303,7 +302,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileDao, SysFile> impleme
                     sysFileDao.updateById(sysFileEntity);
                 } else {
                     // [DB] 创建新的文件
-                    sysFileEntity = uploadEvent(multipartFile, category_id, UPLOAD_TARGET.get());
+                    sysFileEntity = uploadEvent(multipartFile, category_id, is_watermark, UPLOAD_TARGET.get());
                     // [格式化] 封面 (图片类型)
                     sysFileEntity = setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
                 }
@@ -312,7 +311,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileDao, SysFile> impleme
             } else {
                 // 不开启 MD5秒 传,则直接初始化分块
                 // [方法] 上传事件
-                sysFileEntity = uploadEvent(multipartFile, category_id, UPLOAD_TARGET.get());
+                sysFileEntity = uploadEvent(multipartFile, category_id, is_watermark, UPLOAD_TARGET.get());
                 // [格式化] 封面 (图片类型)
                 sysFileEntity = setThumbUrl(sysFileEntity, UPLOAD_THUMB_SIZE.get(), UPLOAD_THUMB_SIZE.get(), StyleEnums.THUMB_BACKGROUND.getValue());
                 return sysFileEntity;

+ 18 - 12
src/main/java/com/backendsys/modules/upload/utils/ObjectKey/ObjectKeyUtil.java

@@ -1,5 +1,6 @@
 package com.backendsys.modules.upload.utils.ObjectKey;
 
+import cn.hutool.core.util.StrUtil;
 import com.backendsys.modules.sdk.douyincloud.tos.service.DouyinTosService;
 import com.backendsys.modules.sdk.tencentcloud.cos.service.TencentCosService;
 import com.backendsys.modules.upload.entity.ObjectKey;
@@ -38,6 +39,7 @@ public class ObjectKeyUtil {
     public ObjectKeyEntity urlToObjectKey(String url) {
         String prefix = null;
         Integer target = -1;
+        if (url == null) return null;
         if (url.startsWith(TENCENT_DOMAIN)) {
             prefix = TENCENT_DOMAIN;
             target = 1;
@@ -46,20 +48,24 @@ public class ObjectKeyUtil {
             target = 3;
         }
         // 去掉协议头(http://、https://)后,再取前缀
-        int domainStart = url.indexOf(prefix);
-        if (domainStart == -1) {
+        if (StrUtil.isNotEmpty(prefix)) {
+            int domainStart = url.indexOf(prefix);
+            if (domainStart == -1) {
+                return null;
+            }
+            // 去掉域名前缀,取后面的路径
+            String object_key = url.substring(domainStart + prefix.length());
+            // 去掉可能的前导斜杠
+            if (object_key.startsWith("/")) {
+                object_key = object_key.substring(1);
+            }
+            ObjectKeyEntity result = new ObjectKeyEntity();
+            result.setObject_key(object_key);
+            result.setTarget(target);
+            return result;
+        } else {
             return null;
         }
-        // 去掉域名前缀,取后面的路径
-        String object_key = url.substring(domainStart + prefix.length());
-        // 去掉可能的前导斜杠
-        if (object_key.startsWith("/")) {
-            object_key = object_key.substring(1);
-        }
-        ObjectKeyEntity result = new ObjectKeyEntity();
-        result.setObject_key(object_key);
-        result.setTarget(target);
-        return result;
     }
 
     /**

+ 8 - 2
src/main/java/com/backendsys/modules/upload/utils/UploadUtil.java

@@ -120,7 +120,12 @@ public class UploadUtil {
     public static String getImageThumbUrl(String url, Integer target, Integer width, Integer height) {
         return getImageThumbUrl(url, target, width, height, StyleEnums.THUMB_BACKGROUND.getValue());
     }
-    public static String getImageThumbUrl(String url, Integer target, Integer width, Integer height, String backgroundColor) {
+    public static String getImageThumbUrl(String url, Integer target, Integer width, Integer height,
+                                          String backgroundColor) {
+        return getImageThumbUrl(url, target, width, height, backgroundColor, false);
+    }
+    public static String getImageThumbUrl(String url, Integer target, Integer width, Integer height,
+                                          String backgroundColor, Boolean is_importent) {
         if (target == -1) {
             UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
             if (width != null) builder.queryParam("w", width);
@@ -133,7 +138,8 @@ public class UploadUtil {
             backgroundColor = "#" + backgroundColor;
             String w = (width != null) ? Convert.toStr(width) : "";
             String h = (height != null) ? Convert.toStr(height) : "";
-            return url + "?imageMogr2/thumbnail/" + w + "x" + h + "/pad/1/color/" + Base64.encode(backgroundColor);
+            return url + "?imageMogr2/thumbnail/" + (w != null ? w : "") + "x" + (h != null ? h : "") +
+                    (is_importent ? "!" : "") + "/pad/1/color/" + Base64.encode(backgroundColor);
         }
         // 抖音云
         if (target == 3) {

+ 1 - 0
src/main/resources/application-dev.yml

@@ -212,6 +212,7 @@ klingai:
 
 comfyui:
   host: 127.0.0.1
+  output: o.daoguyujiamcn.com
   ports: 8000, 8001, 8002, 8003, 8004, 8005, 8006, 8007
   token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
   is-save: true

+ 1 - 0
src/main/resources/application-local.yml

@@ -222,6 +222,7 @@ klingai:
 
 comfyui:
   host: 43.128.1.201
+  output: o.daoguyujiamcn.com
   ports: 8000, 8001, 8002, 8003, 8004, 8005, 8006, 8007
   token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
   is-save: false

+ 1 - 0
src/main/resources/application-prod.yml

@@ -213,6 +213,7 @@ klingai:
 
 comfyui:
   host: 127.0.0.1
+  output: o.daoguyujiamcn.com
   ports: 8000, 8001, 8002, 8003, 8004, 8005, 8006, 8007
   token: $2b$12$.MR4qGaFetN1FPQzbfyIrehsyjnPJ12xAZhR/l7KZpLkUPQTCG4gy
   is-save: true

+ 20 - 7
src/main/resources/mapper/ai/material/MaterialCategoryDao.xml

@@ -3,29 +3,42 @@
 <mapper namespace="com.backendsys.modules.material.dao.MaterialCategoryDao">
 
     <sql id="includeMaterialCategory">
-        id,
-        id category_id,
-        category_name,
-        sort
+        mc.id,
+        mc.id category_id,
+        mc.lora_id,
+        mcl.lora_name,
+        mc.category_name,
+        mc.is_share,
+        mc.sort
     </sql>
     <!-- COALESCE(content_type, '') content_type, -->
     <resultMap id="resultMapMaterialCategory" type="com.backendsys.modules.material.entity.MaterialCategory">
         <id property="id" column="id" jdbcType="BIGINT" />
         <result property="category_id" column="id" javaType="java.lang.Long" />
+        <result property="lora_id" column="lora_id" javaType="java.lang.Long" />
+        <result property="lora_name" column="lora_name" />
         <result property="category_name" column="category_name" />
+        <result property="is_share" column="is_share" javaType="java.lang.Integer"/>
         <result property="sort" column="sort" javaType="java.lang.Integer" />
     </resultMap>
 
     <select id="selectMaterialCategoryList" resultMap="resultMapMaterialCategory">
         SELECT
             <include refid="includeMaterialCategory" />
-        FROM ai_material_category
+        FROM ai_material_category mc
+        LEFT JOIN ai_material_lora mcl ON mc.lora_id = mcl.id
         <where>
+            <if test="lora_id != null and lora_id != ''">
+                AND mc.lora_id = #{lora_id}
+            </if>
             <if test="category_name != null and category_name != ''">
-                AND category_name like concat('%', #{category_name}, '%')
+                AND mc.category_name like concat('%', #{category_name}, '%')
+            </if>
+            <if test="is_share != null and is_share != ''">
+                AND mc.is_share = #{is_share}
             </if>
         </where>
-        ORDER BY sort DESC
+        ORDER BY mc.sort DESC
     </select>
 
 

+ 15 - 13
src/main/resources/mapper/ai/material/MaterialDao.xml

@@ -26,8 +26,8 @@
         m.material_name,
         COALESCE(m.image_thumb_url, '') image_thumb_url,
         COALESCE(m.image_url, '') image_url,
-        COALESCE(m.fla_url, '') fla_url,
-        COALESCE(m.psd_url, '') psd_url,
+        COALESCE(m.file_url, '') file_url,
+        COALESCE(m.pan_baidu_url, '') pan_baidu_url,
         m.is_copyright,
         m.create_time,
         m.update_time
@@ -61,8 +61,8 @@
         <result property="material_name" column="material_name" />
         <result property="image_thumb_url" column="image_thumb_url" />
         <result property="image_url" column="image_url" />
-        <result property="fla_url" column="fla_url" />
-        <result property="psd_url" column="psd_url" />
+        <result property="file_url" column="file_url" />
+        <result property="pan_baidu_url" column="pan_baidu_url" />
         <result property="is_copyright" column="is_copyright" javaType="java.lang.Integer" />
         <result property="create_time" column="create_time"
                 typeHandler="com.backendsys.config.Mybatis.handler.timezone.LocalDateTimeHandler" />
@@ -110,8 +110,8 @@
             <if test="tag_ids != null and tag_ids != ''">, tag_ids</if>
             <if test="image_thumb_url != null and image_thumb_url != ''">, image_thumb_url</if>
             <if test="image_url != null and image_url != ''">, image_url</if>
-            <if test="fla_url != null and fla_url != ''">, fla_url</if>
-            <if test="psd_url != null and psd_url != ''">, psd_url</if>
+            <if test="file_url != null and file_url != ''">, file_url</if>
+            <if test="pan_baidu_url != null and pan_baidu_url != ''">, pan_baidu_url</if>
             <if test="is_copyright != null and is_copyright != ''">, is_copyright</if>
         )
         VALUES (
@@ -119,8 +119,8 @@
             <if test="tag_ids != null and tag_ids != ''">, #{tag_ids}</if>
             <if test="image_thumb_url != null and image_thumb_url != ''">, #{image_thumb_url}</if>
             <if test="image_url != null and image_url != ''">, #{image_url}</if>
-            <if test="fla_url != null and fla_url != ''">, #{fla_url}</if>
-            <if test="psd_url != null and psd_url != ''">, #{psd_url}</if>
+            <if test="file_url != null and file_url != ''">, #{file_url}</if>
+            <if test="pan_baidu_url != null and pan_baidu_url != ''">, #{pan_baidu_url}</if>
             <if test="is_copyright != null and is_copyright != ''">, #{is_copyright}</if>
        )
     </insert>
@@ -132,11 +132,13 @@
         user_id = #{user_id}
         <if test="category_id != null and category_id != ''">, category_id = #{category_id}</if>
         <if test="material_name != null and material_name != ''">, material_name = #{material_name}</if>
-        <if test="tag_ids != null and tag_ids != ''">, tag_ids = #{tag_ids}</if>
-        <if test="image_thumb_url != null and image_thumb_url != ''">, image_thumb_url = #{image_thumb_url}</if>
-        <if test="image_url != null and image_url != ''">, image_url = #{image_url}</if>
-        <if test="fla_url != null and fla_url != ''">, fla_url = #{fla_url}</if>
-        <if test="psd_url != null and psd_url != ''">, psd_url = #{psd_url}</if>
+        <!-- 允许为空 -->
+        <if test="tag_ids != null">, tag_ids = #{tag_ids}</if>
+        <if test="image_thumb_url != null">, image_thumb_url = #{image_thumb_url}</if>
+        <if test="image_url != null">, image_url = #{image_url}</if>
+        <if test="file_url != null">, file_url = #{file_url}</if>
+        <if test="pan_baidu_url != null">, pan_baidu_url = #{pan_baidu_url}</if>
+        <!-- -->
         <if test="is_copyright != null and is_copyright != ''">, is_copyright = #{is_copyright}</if>
         WHERE id = #{material_id}
     </update>

+ 32 - 0
src/main/resources/mapper/ai/material/MaterialLoraDao.xml

@@ -0,0 +1,32 @@
+<?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.backendsys.modules.material.dao.MaterialLoraDao">
+
+    <sql id="includeMaterialLora">
+        id,
+        id lora_id,
+        lora_name,
+        sort
+    </sql>
+    <!-- COALESCE(content_type, '') content_type, -->
+    <resultMap id="resultMapMaterialLora" type="com.backendsys.modules.material.entity.MaterialLora">
+        <id property="id" column="id" jdbcType="BIGINT" />
+        <result property="lora_id" column="id" javaType="java.lang.Long" />
+        <result property="lora_name" column="lora_name" />
+        <result property="sort" column="sort" javaType="java.lang.Integer" />
+    </resultMap>
+
+    <select id="selectMaterialLoraList" resultMap="resultMapMaterialLora">
+        SELECT
+            <include refid="includeMaterialLora" />
+        FROM ai_material_lora
+        <where>
+            <if test="lora_name != null and lora_name != ''">
+                AND lora_name = #{lora_name}
+            </if>
+        </where>
+        ORDER BY sort DESC
+    </select>
+
+
+</mapper>

+ 1 - 1
src/main/resources/mapper/ai/material/MaterialTagDao.xml

@@ -15,7 +15,7 @@
         <result property="tag_id" column="id" javaType="java.lang.Long" />
         <result property="category_id" column="category_id" javaType="java.lang.Long" />
         <result property="tag_name" column="tag_name" />
-        <result property="material_count" column="material_count" javaType="java.lang.Integer" />
+        <result property="material_count" column="material_count" javaType="java.lang.Long" />
         <result property="sort" column="sort" javaType="java.lang.Integer" />
     </resultMap>
 

+ 12 - 11
src/main/resources/mapper/system/SysUserDao.xml

@@ -288,17 +288,18 @@
         </trim>
         WHERE user_id = #{user_id};
 
-        <!--判断是否有新的数据需要插入-->
-        <if test="role_id != null and role_id.size() > 0">
-            <!--删除符合条件的数据-->
-            DELETE FROM sys_user_role_relation WHERE user_id = #{user_id};
-            <!--插入-->
-            INSERT INTO sys_user_role_relation (user_id, role_id)
-            VALUES
-            <foreach collection="role_id" item="id" separator=",">
-                (#{user_id}, #{id})
-            </foreach>
-        </if>
+        <!-- 禁止在编辑用户信息的时候,修改用户角色 -->
+        <!-- 判断是否有新的数据需要插入 -->
+<!--        <if test="role_id != null and role_id.size() > 0">-->
+<!--            &lt;!&ndash;删除符合条件的数据&ndash;&gt;-->
+<!--            DELETE FROM sys_user_role_relation WHERE user_id = #{user_id};-->
+<!--            &lt;!&ndash;插入&ndash;&gt;-->
+<!--            INSERT INTO sys_user_role_relation (user_id, role_id)-->
+<!--            VALUES-->
+<!--            <foreach collection="role_id" item="id" separator=",">-->
+<!--                (#{user_id}, #{id})-->
+<!--            </foreach>-->
+<!--        </if>-->
     </update>
     <!--删掉全部关联,再重新关联-->
 

+ 1 - 0
src/main/resources/mapper/system/SysUserInfoDao.xml

@@ -71,6 +71,7 @@
             <if test="email != null and email != ''">AND uf.email LIKE CONCAT('%', #{email}, '%')</if>
             <if test="status != null and status != ''">AND uf.status = #{status}</if>
             <if test="audit_status != null and audit_status != ''">AND uf.audit_status = #{audit_status}</if>
+            <if test="invite_code != null and invite_code != ''">AND uf.invite_code = #{invite_code}</if>
             <if test="role_id != null and role_id != ''">
                 AND urr.role_id IN
                 <foreach collection="role_id" item="role" open="(" separator="," close=")">#{role}</foreach>

+ 15 - 1
src/main/resources/mapper/system/SysUserRoleDao.xml

@@ -5,16 +5,20 @@
     <sql id="includeUserRole">
         ur.id id,
         ur.id role_id,
+        ur.role_sign role_sign,
         ur.role_name role_name,
         COALESCE(ur.role_description, '') role_description,
+        COALESCE(ur.login_default_page, '') login_default_page,
         ur.sort sort,
         ur.status status
     </sql>
     <resultMap id="resultMapUserRole" type="java.util.LinkedHashMap">
         <id property="id" column="id" jdbcType="BIGINT" />
         <result property="role_id" column="role_id" javaType="java.lang.Long"/>
+        <result property="role_sign" column="role_sign" />
         <result property="role_name" column="role_name" />
         <result property="role_description" column="role_description" />
+        <result property="login_default_page" column="login_default_page" />
         <result property="user_count" column="user_count" />
         <result property="sort" column="sort" javaType="java.lang.Integer" />
         <result property="status" column="status" javaType="java.lang.Integer" />
@@ -25,6 +29,9 @@
         LEFT JOIN sys_user_role_relation urr ON ur.id = urr.role_id
         LEFT JOIN sys_user us ON us.id = urr.user_id
         <where>
+            <if test="role_sign != null and role_sign != ''">
+                AND ur.role_sign = #{role_sign}
+            </if>
             <if test="role_name != null and role_name != ''">
                 AND ur.role_name LIKE CONCAT('%', #{role_name}, '%')
             </if>
@@ -39,12 +46,16 @@
     <!-- 获得用户角色 (部分字段) -->
     <resultMap id="resultMapUserRoleSimple" type="java.util.LinkedHashMap">
         <result property="role_id" column="role_id" javaType="java.lang.Long" />
+        <result property="role_sign" column="role_sign" />
         <result property="role_name" column="role_name" />
+        <result property="login_default_page" column="login_default_page" />
     </resultMap>
     <select id="selectRoleByUserId" resultMap="resultMapUserRoleSimple">
         SELECT
             surr.role_id role_id,
-            sur.role_name role_name
+            sur.role_sign role_sign,
+            sur.role_name role_name,
+            sur.login_default_page login_default_page
         FROM sys_user_role_relation surr
         LEFT JOIN sys_user_role sur ON sur.id = surr.role_id
         WHERE user_id = #{user_id}
@@ -54,10 +65,12 @@
     <!-- 创建 用户角色 -->
     <insert id="insertUserRole" parameterType="com.backendsys.entity.System.SysUserRoleDTO" useGeneratedKeys="true" keyProperty="id">
         INSERT INTO sys_user_role (role_name
+            <if test="role_sign != null">, role_sign</if>
             <if test="role_description != null">, role_description</if>
             <if test="sort != null">, sort</if>
             <if test="status != null">, status</if>
         ) VALUES (#{role_name}
+            <if test="role_sign != null">, #{role_sign}</if>
             <if test="role_description != null">, #{role_description}</if>
             <if test="sort != null">, #{sort}</if>
             <if test="status != null">, #{status}</if>
@@ -76,6 +89,7 @@
     <update id="updateUserRole" parameterType="com.backendsys.entity.System.SysUserRoleDTO">
         UPDATE sys_user_role SET
         <trim suffixOverrides="," suffix=" ">
+            <if test="role_sign != null and role_sign != ''">role_sign = #{role_sign},</if>
             <if test="role_name != null and role_name != ''">role_name = #{role_name},</if>
             <if test="role_description != null and role_description != ''">role_description = #{role_description},</if>
             <if test="sort != null and sort != ''">sort = #{sort},</if>

+ 9 - 0
src/main/resources/mapper/system/SysUserRoleRelationDao.xml

@@ -8,4 +8,13 @@
         WHERE user_id = #{user_id}
     </select>
 
+    <!-- 批量插入 -->
+    <insert id="insertBatch" parameterType="java.util.List">
+        INSERT INTO sys_user_role_relation (user_id, role_id)
+        VALUES
+        <foreach collection="role_ids" item="role_id" separator=",">
+            (#{user_id}, #{role_id})
+        </foreach>
+    </insert>
+
 </mapper>