提交 0c4531a3 authored 作者: kxjia's avatar kxjia

初始化项目

上级 5ae3d3ed
package com.fintech.penalty.controller;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.service.AnalysisKeywordService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/analysis-keywords")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class AnalysisKeywordController {
private final AnalysisKeywordService keywordService;
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<PageResponse<AnalysisKeywordDTO>>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
PageResponse<AnalysisKeywordDTO> result = keywordService.findAll(page, size);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/enabled")
public ResponseEntity<ApiResponse<List<AnalysisKeywordDTO>>> findAllEnabled() {
List<AnalysisKeywordDTO> result = keywordService.findAllEnabled();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/category/{category}")
public ResponseEntity<ApiResponse<List<AnalysisKeywordDTO>>> findByCategory(@PathVariable String category) {
List<AnalysisKeywordDTO> result = keywordService.findByCategory(category);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<AnalysisKeywordDTO>> findById(@PathVariable Long id) {
AnalysisKeywordDTO result = keywordService.findById(id);
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<AnalysisKeywordDTO>> create(@Valid @RequestBody CreateAnalysisKeywordRequest request) {
try {
AnalysisKeywordDTO result = keywordService.create(request);
return ResponseEntity.ok(ApiResponse.success("创建成功", result));
} catch (Exception e) {
log.warn("创建关键词失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<AnalysisKeywordDTO>> update(
@PathVariable Long id,
@Valid @RequestBody UpdateAnalysisKeywordRequest request) {
try {
AnalysisKeywordDTO result = keywordService.update(id, request);
return ResponseEntity.ok(ApiResponse.success("更新成功", result));
} catch (Exception e) {
log.warn("更新关键词失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
try {
keywordService.delete(id);
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
} catch (Exception e) {
log.warn("删除关键词失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
}
\ No newline at end of file
package com.fintech.penalty.controller;
import com.fintech.penalty.dto.AnalysisConfigDTO;
import com.fintech.penalty.dto.ApiResponse;
import com.fintech.penalty.service.AnalysisConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
@RestController
@RequestMapping("/analysis-config")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class AnalysisConfigController {
private final AnalysisConfigService configService;
@GetMapping("/active")
public ResponseEntity<ApiResponse<AnalysisConfigDTO>> getActiveConfig() {
AnalysisConfigDTO config = configService.getActiveConfig();
return ResponseEntity.ok(ApiResponse.success(config));
}
@PostMapping("/active")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<AnalysisConfigDTO>> setActiveConfig(
@RequestBody AnalysisConfigDTO config,
Principal principal) {
if (principal != null) {
config.setUpdatedBy(1L);
config.setUpdatedAt(java.time.LocalDateTime.now().toString());
}
configService.saveActiveConfig(config);
return ResponseEntity.ok(ApiResponse.success(configService.getActiveConfig()));
}
}
package com.fintech.penalty.controller;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.service.AnalysisKeywordService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/analysis-keywords")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class AnalysisKeywordController {
private final AnalysisKeywordService keywordService;
@GetMapping
public ResponseEntity<ApiResponse<PageResponse<AnalysisKeywordDTO>>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
PageResponse<AnalysisKeywordDTO> result = keywordService.findAll(page, size);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/enabled")
public ResponseEntity<ApiResponse<List<AnalysisKeywordDTO>>> findAllEnabled() {
List<AnalysisKeywordDTO> result = keywordService.findAllEnabled();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/category/{category}")
public ResponseEntity<ApiResponse<List<AnalysisKeywordDTO>>> findByCategory(@PathVariable String category) {
List<AnalysisKeywordDTO> result = keywordService.findByCategory(category);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<AnalysisKeywordDTO>> findById(@PathVariable Long id) {
AnalysisKeywordDTO result = keywordService.findById(id);
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
public ResponseEntity<ApiResponse<AnalysisKeywordDTO>> create(@Valid @RequestBody CreateAnalysisKeywordRequest request) {
try {
AnalysisKeywordDTO result = keywordService.create(request);
return ResponseEntity.ok(ApiResponse.success("创建成功", result));
} catch (Exception e) {
log.warn("创建关键词失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<AnalysisKeywordDTO>> update(
@PathVariable Long id,
@Valid @RequestBody UpdateAnalysisKeywordRequest request) {
try {
AnalysisKeywordDTO result = keywordService.update(id, request);
return ResponseEntity.ok(ApiResponse.success("更新成功", result));
} catch (Exception e) {
log.warn("更新关键词失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
try {
keywordService.delete(id);
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
} catch (Exception e) {
log.warn("删除关键词失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
}
\ No newline at end of file
package com.fintech.penalty.controller;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.service.AnalysisRuleService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/analysis-rules")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class AnalysisRuleController {
private final AnalysisRuleService ruleService;
@GetMapping
public ResponseEntity<ApiResponse<PageResponse<AnalysisRuleDTO>>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
PageResponse<AnalysisRuleDTO> result = ruleService.findAll(page, size);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/enabled")
public ResponseEntity<ApiResponse<List<AnalysisRuleDTO>>> findAllEnabled() {
List<AnalysisRuleDTO> result = ruleService.findAllEnabled();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/match")
public ResponseEntity<ApiResponse<List<AnalysisRuleDTO>>> findMatchingRules(
@RequestParam(required = false) String institutionType,
@RequestParam(required = false) String penaltyType,
@RequestParam(required = false) Long amount) {
List<AnalysisRuleDTO> result = ruleService.findMatchingRules(institutionType, penaltyType, amount);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<AnalysisRuleDTO>> findById(@PathVariable Long id) {
AnalysisRuleDTO result = ruleService.findById(id);
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<AnalysisRuleDTO>> create(@Valid @RequestBody CreateAnalysisRuleRequest request) {
try {
AnalysisRuleDTO result = ruleService.create(request);
return ResponseEntity.ok(ApiResponse.success("创建成功", result));
} catch (Exception e) {
log.warn("创建分析规则失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<AnalysisRuleDTO>> update(
@PathVariable Long id,
@Valid @RequestBody UpdateAnalysisRuleRequest request) {
try {
AnalysisRuleDTO result = ruleService.update(id, request);
return ResponseEntity.ok(ApiResponse.success("更新成功", result));
} catch (Exception e) {
log.warn("更新分析规则失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
try {
ruleService.delete(id);
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
} catch (Exception e) {
log.warn("删除分析规则失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
}
\ No newline at end of file
package com.fintech.penalty.controller;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.service.MenuService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/menus")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class MenuController {
private final MenuService menuService;
@GetMapping
public ResponseEntity<ApiResponse<List<MenuDTO>>> findAll() {
List<MenuDTO> result = menuService.findAll();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/visible")
public ResponseEntity<ApiResponse<List<MenuDTO>>> findVisible() {
List<MenuDTO> result = menuService.findVisible();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/role/{roleCode}")
public ResponseEntity<ApiResponse<List<MenuDTO>>> findByRoleCode(@PathVariable String roleCode) {
List<MenuDTO> result = menuService.findByRoleCode(roleCode);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/children")
public ResponseEntity<ApiResponse<List<MenuDTO>>> findByParentId(
@RequestParam(required = false, defaultValue = "0") Long parentId) {
List<MenuDTO> result = menuService.findByParentId(parentId);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<MenuDTO>> findById(@PathVariable Long id) {
MenuDTO menu = menuService.findById(id);
return ResponseEntity.ok(ApiResponse.success(menu));
}
@PostMapping
public ResponseEntity<ApiResponse<MenuDTO>> createMenu(@Valid @RequestBody CreateMenuRequest request) {
try {
MenuDTO menu = menuService.createMenu(request);
return ResponseEntity.ok(ApiResponse.success("创建成功", menu));
} catch (Exception e) {
log.warn("创建菜单失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<MenuDTO>> updateMenu(
@PathVariable Long id,
@Valid @RequestBody UpdateMenuRequest request) {
try {
MenuDTO menu = menuService.updateMenu(id, request);
return ResponseEntity.ok(ApiResponse.success("更新成功", menu));
} catch (Exception e) {
log.warn("更新菜单失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteMenu(@PathVariable Long id) {
try {
menuService.deleteMenu(id);
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
} catch (Exception e) {
log.warn("删除菜单失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
}
\ No newline at end of file
package com.fintech.penalty.controller;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.service.PermissionService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/permissions")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class PermissionController {
private final PermissionService permissionService;
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<PageResponse<PermissionDTO>>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
PageResponse<PermissionDTO> result = permissionService.findAll(page, size);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/simple")
public ResponseEntity<ApiResponse<List<PermissionDTO>>> findAllSimple() {
List<PermissionDTO> result = permissionService.findAllSimple();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<PermissionDTO>> findById(@PathVariable Long id) {
PermissionDTO permission = permissionService.findById(id);
return ResponseEntity.ok(ApiResponse.success(permission));
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<PermissionDTO>> createPermission(@Valid @RequestBody CreatePermissionRequest request) {
try {
PermissionDTO permission = permissionService.createPermission(request);
return ResponseEntity.ok(ApiResponse.success("创建成功", permission));
} catch (Exception e) {
log.warn("创建权限失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<PermissionDTO>> updatePermission(
@PathVariable Long id,
@Valid @RequestBody UpdatePermissionRequest request) {
try {
PermissionDTO permission = permissionService.updatePermission(id, request);
return ResponseEntity.ok(ApiResponse.success("更新成功", permission));
} catch (Exception e) {
log.warn("更新权限失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<Void>> deletePermission(@PathVariable Long id) {
try {
permissionService.deletePermission(id);
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
} catch (Exception e) {
log.warn("删除权限失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
}
\ No newline at end of file
package com.fintech.penalty.controller;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.service.ReportTemplateService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/report-templates")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class ReportTemplateController {
private final ReportTemplateService templateService;
@GetMapping
public ResponseEntity<ApiResponse<PageResponse<ReportTemplateDTO>>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
PageResponse<ReportTemplateDTO> result = templateService.findAll(page, size);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/enabled")
public ResponseEntity<ApiResponse<List<ReportTemplateDTO>>> findAllEnabled() {
List<ReportTemplateDTO> result = templateService.findAllEnabled();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/default")
public ResponseEntity<ApiResponse<ReportTemplateDTO>> findDefault() {
ReportTemplateDTO result = templateService.findDefault();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<ReportTemplateDTO>> findById(@PathVariable Long id) {
ReportTemplateDTO result = templateService.findById(id);
return ResponseEntity.ok(ApiResponse.success(result));
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<ReportTemplateDTO>> create(@Valid @RequestBody CreateReportTemplateRequest request) {
try {
ReportTemplateDTO result = templateService.create(request);
return ResponseEntity.ok(ApiResponse.success("创建成功", result));
} catch (Exception e) {
log.warn("创建报告模板失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<ReportTemplateDTO>> update(
@PathVariable Long id,
@Valid @RequestBody UpdateReportTemplateRequest request) {
try {
ReportTemplateDTO result = templateService.update(id, request);
return ResponseEntity.ok(ApiResponse.success("更新成功", result));
} catch (Exception e) {
log.warn("更新报告模板失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<ApiResponse<Void>> delete(@PathVariable Long id) {
try {
templateService.delete(id);
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
} catch (Exception e) {
log.warn("删除报告模板失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
}
\ No newline at end of file
package com.fintech.penalty.controller;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.service.RoleService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/roles")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin(origins = "*")
public class RoleController {
private final RoleService roleService;
@GetMapping
public ResponseEntity<ApiResponse<PageResponse<RoleDTO>>> findAll(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
PageResponse<RoleDTO> result = roleService.findAll(page, size);
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/simple")
public ResponseEntity<ApiResponse<List<RoleDTO>>> findAllSimple() {
List<RoleDTO> result = roleService.findAllSimple();
return ResponseEntity.ok(ApiResponse.success(result));
}
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<RoleDTO>> findById(@PathVariable Long id) {
RoleDTO role = roleService.findById(id);
return ResponseEntity.ok(ApiResponse.success(role));
}
@PostMapping
public ResponseEntity<ApiResponse<RoleDTO>> createRole(@Valid @RequestBody CreateRoleRequest request) {
try {
RoleDTO role = roleService.createRole(request);
return ResponseEntity.ok(ApiResponse.success("创建成功", role));
} catch (Exception e) {
log.warn("创建角色失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<RoleDTO>> updateRole(
@PathVariable Long id,
@Valid @RequestBody UpdateRoleRequest request) {
try {
RoleDTO role = roleService.updateRole(id, request);
return ResponseEntity.ok(ApiResponse.success("更新成功", role));
} catch (Exception e) {
log.warn("更新角色失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteRole(@PathVariable Long id) {
try {
roleService.deleteRole(id);
return ResponseEntity.ok(ApiResponse.success("删除成功", null));
} catch (Exception e) {
log.warn("删除角色失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@GetMapping("/{id}/menus")
public ResponseEntity<ApiResponse<List<Long>>> getRoleMenus(@PathVariable Long id) {
List<Long> menuIds = roleService.getRoleMenuIds(id);
return ResponseEntity.ok(ApiResponse.success(menuIds));
}
@PutMapping("/{id}/menus")
public ResponseEntity<ApiResponse<Void>> saveRoleMenus(
@PathVariable Long id,
@RequestBody Map<String, List<Long>> body) {
try {
roleService.saveRoleMenus(id, body.get("menuIds"));
return ResponseEntity.ok(ApiResponse.success("保存成功", null));
} catch (Exception e) {
log.warn("保存角色菜单失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@GetMapping("/{id}/users")
public ResponseEntity<ApiResponse<List<Long>>> getRoleUsers(@PathVariable Long id) {
List<Long> userIds = roleService.getRoleUserIds(id);
return ResponseEntity.ok(ApiResponse.success(userIds));
}
@PostMapping("/assign-user")
public ResponseEntity<ApiResponse<Void>> assignUserToRole(@RequestBody Map<String, Object> body) {
try {
Long userId = Long.valueOf(body.get("userId").toString());
String roleCode = body.get("roleCode").toString();
roleService.assignUserToRole(userId, roleCode);
return ResponseEntity.ok(ApiResponse.success("分配成功", null));
} catch (Exception e) {
log.warn("分配用户到角色失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
@DeleteMapping("/user/{userId}/role")
public ResponseEntity<ApiResponse<Void>> removeUserFromRole(@PathVariable Long userId) {
try {
roleService.removeUserFromRole(userId);
return ResponseEntity.ok(ApiResponse.success("移除成功", null));
} catch (Exception e) {
log.warn("移除用户角色失败: {}", e.getMessage());
return ResponseEntity.ok(ApiResponse.error(400, e.getMessage()));
}
}
}
\ No newline at end of file
package com.fintech.penalty.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnalysisConfigDTO {
private Long templateId;
private String templateName;
private List<Long> ruleIds;
private List<Long> keywordIds;
private Long updatedBy;
private String updatedAt;
}
package com.fintech.penalty.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnalysisKeywordDTO {
private Long id;
private String keyword;
private String category;
private String level;
private String description;
private Boolean enabled;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnalysisRuleDTO {
private Long id;
private String name;
private String description;
private String institutionType;
private String penaltyType;
private Long minAmount;
private Long maxAmount;
private String prompt;
private Boolean enabled;
private Integer priority;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import lombok.Data;
@Data
public class AnalyzeRequest {
private Long templateId;
}
package com.fintech.penalty.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CreateAnalysisKeywordRequest {
@NotBlank(message = "关键词不能为空")
@Size(max = 100, message = "关键词长度不能超过100个字符")
private String keyword;
@Size(max = 50, message = "分类长度不能超过50个字符")
private String category;
@Size(max = 50, message = "级别长度不能超过50个字符")
private String level;
@Size(max = 500, message = "描述长度不能超过500个字符")
private String description;
private Boolean enabled;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CreateAnalysisRuleRequest {
@NotBlank(message = "规则名称不能为空")
@Size(max = 100, message = "规则名称长度不能超过100个字符")
private String name;
@Size(max = 200, message = "描述长度不能超过200个字符")
private String description;
private String institutionType;
private String penaltyType;
private Long minAmount;
private Long maxAmount;
@NotBlank(message = "Prompt不能为空")
private String prompt;
private Boolean enabled;
private Integer priority;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CreateMenuRequest {
@NotBlank(message = "菜单名称不能为空")
@Size(max = 100, message = "菜单名称长度不能超过100个字符")
private String name;
@Size(max = 100, message = "路径长度不能超过100个字符")
private String path;
@Size(max = 50, message = "图标长度不能超过50个字符")
private String icon;
@Size(max = 100, message = "组件路径长度不能超过100个字符")
private String component;
private Long parentId;
private Integer sortOrder;
private Boolean visible;
private Boolean enabled;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CreatePermissionRequest {
@NotBlank(message = "权限编码不能为空")
@Size(max = 100, message = "权限编码长度不能超过100个字符")
private String code;
@NotBlank(message = "权限名称不能为空")
@Size(max = 100, message = "权限名称长度不能超过100个字符")
private String name;
@Size(max = 50, message = "权限类型长度不能超过50个字符")
private String type;
@Size(max = 200, message = "资源长度不能超过200个字符")
private String resource;
@Size(max = 500, message = "描述长度不能超过500个字符")
private String description;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CreateReportTemplateRequest {
@NotBlank(message = "模板名称不能为空")
@Size(max = 100, message = "模板名称长度不能超过100个字符")
private String name;
@Size(max = 200, message = "描述长度不能超过200个字符")
private String description;
@NotBlank(message = "模板内容不能为空")
private String content;
private Boolean isDefault;
private Boolean enabled;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Data
public class CreateRoleRequest {
@NotBlank(message = "角色编码不能为空")
@Size(min = 2, max = 50, message = "角色编码长度必须在2-50个字符之间")
private String code;
@NotBlank(message = "角色名称不能为空")
@Size(max = 100, message = "角色名称长度不能超过100个字符")
private String name;
@Size(max = 500, message = "描述长度不能超过500个字符")
private String description;
private List<String> permissions;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MenuDTO {
private Long id;
private String name;
private String path;
private String icon;
private String component;
private Long parentId;
private Integer sortOrder;
private Boolean visible;
private Boolean enabled;
private List<MenuDTO> children;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PermissionDTO {
private Long id;
private String code;
private String name;
private String type;
private String resource;
private String description;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ReportTemplateDTO {
private Long id;
private String name;
private String description;
private String content;
private Boolean isDefault;
private Boolean enabled;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleDTO {
private Long id;
private String code;
private String name;
private String description;
private List<String> permissions;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UpdateAnalysisKeywordRequest {
@Size(max = 100, message = "关键词长度不能超过100个字符")
private String keyword;
@Size(max = 50, message = "分类长度不能超过50个字符")
private String category;
@Size(max = 50, message = "级别长度不能超过50个字符")
private String level;
@Size(max = 500, message = "描述长度不能超过500个字符")
private String description;
private Boolean enabled;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UpdateAnalysisRuleRequest {
@Size(max = 100, message = "规则名称长度不能超过100个字符")
private String name;
@Size(max = 200, message = "描述长度不能超过200个字符")
private String description;
private String institutionType;
private String penaltyType;
private Long minAmount;
private Long maxAmount;
private String prompt;
private Boolean enabled;
private Integer priority;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UpdateMenuRequest {
@Size(max = 100, message = "菜单名称长度不能超过100个字符")
private String name;
@Size(max = 100, message = "路径长度不能超过100个字符")
private String path;
@Size(max = 50, message = "图标长度不能超过50个字符")
private String icon;
@Size(max = 100, message = "组件路径长度不能超过100个字符")
private String component;
private Long parentId;
private Integer sortOrder;
private Boolean visible;
private Boolean enabled;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UpdatePermissionRequest {
@Size(max = 100, message = "权限名称长度不能超过100个字符")
private String name;
@Size(max = 50, message = "权限类型长度不能超过50个字符")
private String type;
@Size(max = 200, message = "资源长度不能超过200个字符")
private String resource;
@Size(max = 500, message = "描述长度不能超过500个字符")
private String description;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UpdateReportTemplateRequest {
@Size(max = 100, message = "模板名称长度不能超过100个字符")
private String name;
@Size(max = 200, message = "描述长度不能超过200个字符")
private String description;
private String content;
private Boolean isDefault;
private Boolean enabled;
}
\ No newline at end of file
package com.fintech.penalty.dto;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Data
public class UpdateRoleRequest {
@Size(max = 100, message = "角色名称长度不能超过100个字符")
private String name;
@Size(max = 500, message = "描述长度不能超过500个字符")
private String description;
private List<String> permissions;
}
\ No newline at end of file
package com.fintech.penalty.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "analysis_keywords")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnalysisKeyword {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String keyword;
@Column(length = 50)
private String category;
@Column(length = 50)
private String level;
@Column(columnDefinition = "TEXT")
private String description;
@Column(name = "is_enabled")
@Builder.Default
private Boolean enabled = true;
@Column(name = "create_time", updatable = false)
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@PrePersist
protected void onCreate() {
createTime = LocalDateTime.now();
updateTime = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updateTime = LocalDateTime.now();
}
}
\ No newline at end of file
package com.fintech.penalty.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "analysis_rules")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnalysisRule {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(length = 200)
private String description;
@Column(name = "institution_type")
private String institutionType;
@Column(name = "penalty_type")
private String penaltyType;
@Column(name = "min_amount")
private Long minAmount;
@Column(name = "max_amount")
private Long maxAmount;
@Column(columnDefinition = "TEXT")
private String prompt;
@Column(name = "is_enabled")
@Builder.Default
private Boolean enabled = true;
@Column(name = "priority")
@Builder.Default
private Integer priority = 0;
@Column(name = "create_time", updatable = false)
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@PrePersist
protected void onCreate() {
createTime = LocalDateTime.now();
updateTime = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updateTime = LocalDateTime.now();
}
}
\ No newline at end of file
package com.fintech.penalty.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "menus")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Menu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(length = 100)
private String path;
@Column(length = 50)
private String icon;
@Column(length = 100)
private String component;
@Column(name = "parent_id")
private Long parentId;
@Column(name = "sort_order")
@Builder.Default
private Integer sortOrder = 0;
@Column(name = "is_visible")
@Builder.Default
private Boolean visible = true;
@Column(name = "is_enabled")
@Builder.Default
private Boolean enabled = true;
@Column(name = "create_time", updatable = false)
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@PrePersist
protected void onCreate() {
createTime = LocalDateTime.now();
updateTime = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updateTime = LocalDateTime.now();
}
}
\ No newline at end of file
package com.fintech.penalty.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "permissions")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 100)
private String code;
@Column(nullable = false, length = 100)
private String name;
@Column(length = 50)
private String type;
@Column(length = 200)
private String resource;
@Column(length = 500)
private String description;
@Column(name = "create_time", updatable = false)
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@PrePersist
protected void onCreate() {
createTime = LocalDateTime.now();
updateTime = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updateTime = LocalDateTime.now();
}
}
\ No newline at end of file
package com.fintech.penalty.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "report_templates")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ReportTemplate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(length = 200)
private String description;
@Column(columnDefinition = "TEXT")
private String content;
@Column(name = "is_default")
@Builder.Default
private Boolean isDefault = false;
@Column(name = "is_enabled")
@Builder.Default
private Boolean enabled = true;
@Column(name = "create_time", updatable = false)
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@PrePersist
protected void onCreate() {
createTime = LocalDateTime.now();
updateTime = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updateTime = LocalDateTime.now();
}
}
\ No newline at end of file
package com.fintech.penalty.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "roles")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String code;
@Column(nullable = false, length = 100)
private String name;
@Column(length = 500)
private String description;
@Column(name = "create_time", updatable = false)
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@PrePersist
protected void onCreate() {
createTime = LocalDateTime.now();
updateTime = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updateTime = LocalDateTime.now();
}
}
\ No newline at end of file
package com.fintech.penalty.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "role_menus")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleMenu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "role_id", nullable = false)
private Long roleId;
@Column(name = "menu_id", nullable = false)
private Long menuId;
}
\ No newline at end of file
package com.fintech.penalty.repository;
import com.fintech.penalty.entity.AnalysisKeyword;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AnalysisKeywordRepository extends JpaRepository<AnalysisKeyword, Long> {
List<AnalysisKeyword> findByEnabledTrue();
List<AnalysisKeyword> findByEnabledTrueAndCategory(String category);
List<AnalysisKeyword> findByEnabledTrueAndLevel(String level);
}
\ No newline at end of file
package com.fintech.penalty.repository;
import com.fintech.penalty.entity.AnalysisRule;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface AnalysisRuleRepository extends JpaRepository<AnalysisRule, Long> {
List<AnalysisRule> findByEnabledTrueOrderByPriorityDesc();
@Query("SELECT r FROM AnalysisRule r WHERE r.enabled = true AND " +
"(r.institutionType IS NULL OR r.institutionType = :institutionType) AND " +
"(r.penaltyType IS NULL OR r.penaltyType = :penaltyType) AND " +
"(r.minAmount IS NULL OR r.minAmount <= :amount) AND " +
"(r.maxAmount IS NULL OR r.maxAmount >= :amount) " +
"ORDER BY r.priority DESC")
List<AnalysisRule> findMatchingRules(String institutionType, String penaltyType, Long amount);
Optional<AnalysisRule> findFirstByEnabledTrueOrderByPriorityDesc();
}
\ No newline at end of file
package com.fintech.penalty.repository;
import com.fintech.penalty.entity.Menu;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MenuRepository extends JpaRepository<Menu, Long> {
List<Menu> findByParentIdOrderBySortOrderAsc(Long parentId);
List<Menu> findByParentIdIsNullOrderBySortOrderAsc();
@Query("SELECT m FROM Menu m WHERE m.visible = true AND m.enabled = true ORDER BY m.sortOrder ASC")
List<Menu> findAllVisible();
@Query("SELECT m FROM Menu m WHERE m.visible = true AND m.enabled = true AND (m.parentId IS NULL OR m.parentId = 0) ORDER BY m.sortOrder ASC")
List<Menu> findRootMenusVisible();
boolean existsByName(String name);
}
\ No newline at end of file
package com.fintech.penalty.repository;
import com.fintech.penalty.entity.Permission;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface PermissionRepository extends JpaRepository<Permission, Long> {
Optional<Permission> findByCode(String code);
boolean existsByCode(String code);
List<Permission> findByCodeIn(List<String> codes);
}
\ No newline at end of file
package com.fintech.penalty.repository;
import com.fintech.penalty.entity.ReportTemplate;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface ReportTemplateRepository extends JpaRepository<ReportTemplate, Long> {
List<ReportTemplate> findByEnabledTrue();
Optional<ReportTemplate> findByIsDefaultTrue();
Optional<ReportTemplate> findByIsDefaultTrueAndEnabledTrue();
}
\ No newline at end of file
package com.fintech.penalty.repository;
import com.fintech.penalty.entity.RoleMenu;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface RoleMenuRepository extends JpaRepository<RoleMenu, Long> {
List<RoleMenu> findByRoleId(Long roleId);
void deleteByRoleId(Long roleId);
}
\ No newline at end of file
package com.fintech.penalty.repository;
import com.fintech.penalty.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByCode(String code);
boolean existsByCode(String code);
}
\ No newline at end of file
package com.fintech.penalty.service;
import com.fintech.penalty.dto.AnalysisConfigDTO;
import com.fintech.penalty.entity.SystemConfig;
import com.fintech.penalty.repository.SystemConfigRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Service
@RequiredArgsConstructor
@Slf4j
public class AnalysisConfigService {
private static final String CONFIG_KEY = "analysis_config";
private final SystemConfigRepository configRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
public AnalysisConfigDTO getActiveConfig() {
return configRepository.findByConfigKey(CONFIG_KEY)
.map(config -> {
try {
return objectMapper.readValue(config.getConfigValue(), AnalysisConfigDTO.class);
} catch (Exception e) {
log.error("解析分析配置失败", e);
return null;
}
})
.orElse(null);
}
public void saveActiveConfig(AnalysisConfigDTO config) {
try {
String json = objectMapper.writeValueAsString(config);
SystemConfig systemConfig = configRepository.findByConfigKey(CONFIG_KEY)
.orElse(SystemConfig.builder().configKey(CONFIG_KEY).build());
systemConfig.setConfigValue(json);
systemConfig.setConfigType("json");
systemConfig.setConfigDesc("当前分析配置: 模板ID、规则ID列表、关键词ID列表");
systemConfig.setUpdatedAt(LocalDateTime.now());
configRepository.save(systemConfig);
log.info("保存分析配置成功");
} catch (Exception e) {
log.error("保存分析配置失败", e);
throw new RuntimeException("保存分析配置失败: " + e.getMessage());
}
}
}
package com.fintech.penalty.service;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.entity.AnalysisKeyword;
import com.fintech.penalty.repository.AnalysisKeywordRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class AnalysisKeywordService {
private final AnalysisKeywordRepository keywordRepository;
public PageResponse<AnalysisKeywordDTO> findAll(int page, int size) {
Page<AnalysisKeyword> pageResult = keywordRepository.findAll(PageRequest.of(page, size));
List<AnalysisKeywordDTO> content = pageResult.getContent().stream()
.map(this::toDTO)
.collect(Collectors.toList());
return PageResponse.<AnalysisKeywordDTO>builder()
.content(content)
.totalElements(pageResult.getTotalElements())
.totalPages(pageResult.getTotalPages())
.pageNumber(page)
.pageSize(size)
.hasNext(pageResult.hasNext())
.hasPrevious(pageResult.hasPrevious())
.build();
}
public List<AnalysisKeywordDTO> findAllEnabled() {
return keywordRepository.findByEnabledTrue().stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
public List<AnalysisKeywordDTO> findByCategory(String category) {
return keywordRepository.findByEnabledTrueAndCategory(category).stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
public AnalysisKeywordDTO findById(Long id) {
return keywordRepository.findById(id)
.map(this::toDTO)
.orElse(null);
}
@Transactional
public AnalysisKeywordDTO create(CreateAnalysisKeywordRequest request) {
AnalysisKeyword keyword = AnalysisKeyword.builder()
.keyword(request.getKeyword())
.category(request.getCategory())
.level(request.getLevel())
.description(request.getDescription())
.enabled(request.getEnabled() != null ? request.getEnabled() : true)
.build();
AnalysisKeyword saved = keywordRepository.save(keyword);
log.info("创建关键词: {}", saved.getKeyword());
return toDTO(saved);
}
@Transactional
public AnalysisKeywordDTO update(Long id, UpdateAnalysisKeywordRequest request) {
AnalysisKeyword keyword = keywordRepository.findById(id)
.orElseThrow(() -> new RuntimeException("关键词不存在"));
if (request.getKeyword() != null) {
keyword.setKeyword(request.getKeyword());
}
if (request.getCategory() != null) {
keyword.setCategory(request.getCategory());
}
if (request.getLevel() != null) {
keyword.setLevel(request.getLevel());
}
if (request.getDescription() != null) {
keyword.setDescription(request.getDescription());
}
if (request.getEnabled() != null) {
keyword.setEnabled(request.getEnabled());
}
AnalysisKeyword saved = keywordRepository.save(keyword);
log.info("更新关键词: {}", saved.getKeyword());
return toDTO(saved);
}
@Transactional
public void delete(Long id) {
AnalysisKeyword keyword = keywordRepository.findById(id)
.orElseThrow(() -> new RuntimeException("关键词不存在"));
keywordRepository.delete(keyword);
log.info("删除关键词: {}", keyword.getKeyword());
}
private AnalysisKeywordDTO toDTO(AnalysisKeyword keyword) {
return AnalysisKeywordDTO.builder()
.id(keyword.getId())
.keyword(keyword.getKeyword())
.category(keyword.getCategory())
.level(keyword.getLevel())
.description(keyword.getDescription())
.enabled(keyword.getEnabled())
.createTime(keyword.getCreateTime())
.updateTime(keyword.getUpdateTime())
.build();
}
}
\ No newline at end of file
package com.fintech.penalty.service;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.entity.AnalysisRule;
import com.fintech.penalty.repository.AnalysisRuleRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class AnalysisRuleService {
private final AnalysisRuleRepository ruleRepository;
public PageResponse<AnalysisRuleDTO> findAll(int page, int size) {
Page<AnalysisRule> pageResult = ruleRepository.findAll(PageRequest.of(page, size));
List<AnalysisRuleDTO> content = pageResult.getContent().stream()
.map(this::toDTO)
.collect(Collectors.toList());
return PageResponse.<AnalysisRuleDTO>builder()
.content(content)
.totalElements(pageResult.getTotalElements())
.totalPages(pageResult.getTotalPages())
.pageNumber(page)
.pageSize(size)
.hasNext(pageResult.hasNext())
.hasPrevious(pageResult.hasPrevious())
.build();
}
public List<AnalysisRuleDTO> findAllEnabled() {
return ruleRepository.findByEnabledTrueOrderByPriorityDesc().stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
public AnalysisRuleDTO findById(Long id) {
return ruleRepository.findById(id)
.map(this::toDTO)
.orElse(null);
}
public List<AnalysisRuleDTO> findMatchingRules(String institutionType, String penaltyType, Long amount) {
return ruleRepository.findMatchingRules(institutionType, penaltyType, amount).stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
@Transactional
public AnalysisRuleDTO create(CreateAnalysisRuleRequest request) {
AnalysisRule rule = AnalysisRule.builder()
.name(request.getName())
.description(request.getDescription())
.institutionType(request.getInstitutionType())
.penaltyType(request.getPenaltyType())
.minAmount(request.getMinAmount())
.maxAmount(request.getMaxAmount())
.prompt(request.getPrompt())
.enabled(request.getEnabled() != null ? request.getEnabled() : true)
.priority(request.getPriority() != null ? request.getPriority() : 0)
.build();
AnalysisRule saved = ruleRepository.save(rule);
log.info("创建分析规则: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public AnalysisRuleDTO update(Long id, UpdateAnalysisRuleRequest request) {
AnalysisRule rule = ruleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("规则不存在"));
if (request.getName() != null) {
rule.setName(request.getName());
}
if (request.getDescription() != null) {
rule.setDescription(request.getDescription());
}
if (request.getInstitutionType() != null) {
rule.setInstitutionType(request.getInstitutionType());
}
if (request.getPenaltyType() != null) {
rule.setPenaltyType(request.getPenaltyType());
}
if (request.getMinAmount() != null) {
rule.setMinAmount(request.getMinAmount());
}
if (request.getMaxAmount() != null) {
rule.setMaxAmount(request.getMaxAmount());
}
if (request.getPrompt() != null) {
rule.setPrompt(request.getPrompt());
}
if (request.getEnabled() != null) {
rule.setEnabled(request.getEnabled());
}
if (request.getPriority() != null) {
rule.setPriority(request.getPriority());
}
AnalysisRule saved = ruleRepository.save(rule);
log.info("更新分析规则: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public void delete(Long id) {
AnalysisRule rule = ruleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("规则不存在"));
ruleRepository.delete(rule);
log.info("删除分析规则: {}", rule.getName());
}
private AnalysisRuleDTO toDTO(AnalysisRule rule) {
return AnalysisRuleDTO.builder()
.id(rule.getId())
.name(rule.getName())
.description(rule.getDescription())
.institutionType(rule.getInstitutionType())
.penaltyType(rule.getPenaltyType())
.minAmount(rule.getMinAmount())
.maxAmount(rule.getMaxAmount())
.prompt(rule.getPrompt())
.enabled(rule.getEnabled())
.priority(rule.getPriority())
.createTime(rule.getCreateTime())
.updateTime(rule.getUpdateTime())
.build();
}
}
\ No newline at end of file
package com.fintech.penalty.service;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.entity.Menu;
import com.fintech.penalty.entity.RoleMenu;
import com.fintech.penalty.repository.MenuRepository;
import com.fintech.penalty.repository.RoleMenuRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class MenuService {
private final MenuRepository menuRepository;
private final RoleMenuRepository roleMenuRepository;
public List<MenuDTO> findAll() {
List<Menu> allMenus = menuRepository.findAll();
return buildTree(allMenus);
}
public List<MenuDTO> findVisible() {
List<Menu> allMenus = menuRepository.findAllVisible();
return buildTree(allMenus);
}
public List<MenuDTO> findByParentId(Long parentId) {
List<Menu> menus = menuRepository.findByParentIdOrderBySortOrderAsc(parentId);
return menus.stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
public List<MenuDTO> findByRoleId(Long roleId) {
List<Long> menuIds = roleMenuRepository.findByRoleId(roleId).stream()
.map(RoleMenu::getMenuId)
.collect(Collectors.toList());
if (menuIds.isEmpty()) {
return new ArrayList<>();
}
List<Menu> menus = menuRepository.findAllById(menuIds);
return buildTree(menus);
}
@Transactional
public void initDefaultMenus() {
List<Menu> existingMenus = menuRepository.findAll();
if (!existingMenus.isEmpty()) {
log.info("Menus already exist, skipping initialization");
return;
}
log.info("Initializing default menus");
// Create parent menus first
List<Menu> parentMenus = List.of(
Menu.builder().name("首页").path("/dashboard").icon("Odometer").component("Dashboard").parentId(null).sortOrder(1).visible(true).enabled(true).build(),
Menu.builder().name("处罚列表").path("/penalties").icon("Document").component("PenaltyList").parentId(null).sortOrder(2).visible(true).enabled(true).build(),
Menu.builder().name("爬取管理").path("/crawl").icon("Monitor").component("CrawlManage").parentId(null).sortOrder(3).visible(true).enabled(true).build(),
Menu.builder().name("数据统计").path("/statistics").icon("DataAnalysis").component("Statistics").parentId(null).sortOrder(4).visible(true).enabled(true).build()
);
List<Menu> savedParents = menuRepository.saveAll(parentMenus);
// Create system management parent
Menu systemMenu = menuRepository.save(Menu.builder()
.name("系统管理").path("").icon("Setting").component(null).parentId(null).sortOrder(5).visible(true).enabled(true).build());
// Create child menus under system management
Long systemId = systemMenu.getId();
List<Menu> childMenus = List.of(
Menu.builder().name("用户管理").path("/users").icon("User").component("UserManage").parentId(systemId).sortOrder(1).visible(true).enabled(true).build(),
Menu.builder().name("角色管理").path("/roles").icon("UserFilled").component("RoleManage").parentId(systemId).sortOrder(2).visible(true).enabled(true).build(),
Menu.builder().name("菜单管理").path("/menus").icon("Menu").component("MenuManage").parentId(systemId).sortOrder(3).visible(true).enabled(true).build(),
Menu.builder().name("权限管理").path("/permissions").icon("Key").component("PermissionManage").parentId(systemId).sortOrder(4).visible(true).enabled(true).build(),
Menu.builder().name("报告配置").path("/report-config").icon("Document").component("ReportConfig").parentId(systemId).sortOrder(5).visible(true).enabled(true).build(),
Menu.builder().name("角色菜单").path("/role-menu").icon("Connection").component("RoleMenuManage").parentId(systemId).sortOrder(6).visible(true).enabled(true).build(),
Menu.builder().name("角色用户").path("/role-user").icon("UserFilled").component("RoleUserManage").parentId(systemId).sortOrder(7).visible(true).enabled(true).build()
);
menuRepository.saveAll(childMenus);
log.info("Default menus initialized");
}
public List<MenuDTO> findByRoleCode(String roleCode) {
if (roleCode == null) {
roleCode = "ADMIN";
}
List<Menu> allMenus = menuRepository.findAllVisible();
if (allMenus.isEmpty()) {
log.warn("No menus found in database");
return new ArrayList<>();
}
if ("ADMIN".equals(roleCode)) {
return buildTree(allMenus);
}
return buildTree(allMenus.stream()
.filter(m -> m.getVisible() != null && m.getVisible())
.collect(Collectors.toList()));
}
public MenuDTO findById(Long id) {
Menu menu = menuRepository.findById(id)
.orElseThrow(() -> new RuntimeException("菜单不存在"));
return toDTO(menu);
}
@Transactional
public MenuDTO createMenu(CreateMenuRequest request) {
if (menuRepository.existsByName(request.getName())) {
throw new RuntimeException("菜单名称已存在");
}
Menu menu = Menu.builder()
.name(request.getName())
.path(request.getPath())
.icon(request.getIcon())
.component(request.getComponent())
.parentId(request.getParentId())
.sortOrder(request.getSortOrder() != null ? request.getSortOrder() : 0)
.visible(request.getVisible() != null ? request.getVisible() : true)
.enabled(request.getEnabled() != null ? request.getEnabled() : true)
.build();
Menu saved = menuRepository.save(menu);
log.info("创建菜单: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public MenuDTO updateMenu(Long id, UpdateMenuRequest request) {
Menu menu = menuRepository.findById(id)
.orElseThrow(() -> new RuntimeException("菜单不存在"));
if (request.getName() != null) {
menu.setName(request.getName());
}
if (request.getPath() != null) {
menu.setPath(request.getPath());
}
if (request.getIcon() != null) {
menu.setIcon(request.getIcon());
}
if (request.getComponent() != null) {
menu.setComponent(request.getComponent());
}
if (request.getParentId() != null) {
menu.setParentId(request.getParentId());
}
if (request.getSortOrder() != null) {
menu.setSortOrder(request.getSortOrder());
}
if (request.getVisible() != null) {
menu.setVisible(request.getVisible());
}
if (request.getEnabled() != null) {
menu.setEnabled(request.getEnabled());
}
Menu saved = menuRepository.save(menu);
log.info("更新菜单: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public void deleteMenu(Long id) {
Menu menu = menuRepository.findById(id)
.orElseThrow(() -> new RuntimeException("菜单不存在"));
if (!menuRepository.findByParentIdOrderBySortOrderAsc(id).isEmpty()) {
throw new RuntimeException("请先删除子菜单");
}
menuRepository.delete(menu);
log.info("删除菜单: {}", menu.getName());
}
private List<MenuDTO> buildTree(List<Menu> menus) {
if (menus == null || menus.isEmpty()) {
log.warn("buildTree received empty menu list");
return new ArrayList<>();
}
log.debug("Building menu tree from {} menus", menus.size());
Map<Long, List<Menu>> parentMap = menus.stream()
.collect(Collectors.groupingBy(m -> {
Long pid = m.getParentId();
return pid == null ? 0L : pid;
}));
log.debug("Parent map keys: {}", parentMap.keySet());
List<MenuDTO> roots = menus.stream()
.filter(m -> m.getParentId() == null || m.getParentId() == 0)
.sorted(Comparator.comparingInt(Menu::getSortOrder))
.map(m -> buildTreeNode(m, parentMap))
.collect(Collectors.toList());
log.debug("Built {} root menus", roots.size());
return roots;
}
private MenuDTO buildTreeNode(Menu menu, Map<Long, List<Menu>> parentMap) {
MenuDTO dto = toDTO(menu);
List<Menu> children = parentMap.get(menu.getId());
if (children != null && !children.isEmpty()) {
dto.setChildren(children.stream()
.sorted(Comparator.comparingInt(Menu::getSortOrder))
.map(m -> buildTreeNode(m, parentMap))
.collect(Collectors.toList()));
}
return dto;
}
private MenuDTO toDTO(Menu menu) {
return MenuDTO.builder()
.id(menu.getId())
.name(menu.getName())
.path(menu.getPath())
.icon(menu.getIcon())
.component(menu.getComponent())
.parentId(menu.getParentId())
.sortOrder(menu.getSortOrder())
.visible(menu.getVisible())
.enabled(menu.getEnabled())
.createTime(menu.getCreateTime())
.updateTime(menu.getUpdateTime())
.build();
}
}
\ No newline at end of file
package com.fintech.penalty.service;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.entity.Permission;
import com.fintech.penalty.repository.PermissionRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class PermissionService {
private final PermissionRepository permissionRepository;
public PageResponse<PermissionDTO> findAll(int page, int size) {
Page<Permission> pageResult = permissionRepository.findAll(PageRequest.of(page, size));
List<PermissionDTO> content = pageResult.getContent().stream()
.map(this::toDTO)
.collect(Collectors.toList());
return PageResponse.<PermissionDTO>builder()
.content(content)
.totalElements(pageResult.getTotalElements())
.totalPages(pageResult.getTotalPages())
.pageNumber(page)
.pageSize(size)
.hasNext(pageResult.hasNext())
.hasPrevious(pageResult.hasPrevious())
.build();
}
public List<PermissionDTO> findAllSimple() {
return permissionRepository.findAll().stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
public PermissionDTO findById(Long id) {
Permission permission = permissionRepository.findById(id)
.orElseThrow(() -> new RuntimeException("权限不存在"));
return toDTO(permission);
}
@Transactional
public PermissionDTO createPermission(CreatePermissionRequest request) {
if (permissionRepository.existsByCode(request.getCode())) {
throw new RuntimeException("权限编码已存在");
}
Permission permission = Permission.builder()
.code(request.getCode())
.name(request.getName())
.type(request.getType())
.resource(request.getResource())
.description(request.getDescription())
.build();
Permission saved = permissionRepository.save(permission);
log.info("创建权限: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public PermissionDTO updatePermission(Long id, UpdatePermissionRequest request) {
Permission permission = permissionRepository.findById(id)
.orElseThrow(() -> new RuntimeException("权限不存在"));
if (request.getName() != null) {
permission.setName(request.getName());
}
if (request.getType() != null) {
permission.setType(request.getType());
}
if (request.getResource() != null) {
permission.setResource(request.getResource());
}
if (request.getDescription() != null) {
permission.setDescription(request.getDescription());
}
Permission saved = permissionRepository.save(permission);
log.info("更新权限: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public void deletePermission(Long id) {
Permission permission = permissionRepository.findById(id)
.orElseThrow(() -> new RuntimeException("权限不存在"));
permissionRepository.delete(permission);
log.info("删除权限: {}", permission.getName());
}
private PermissionDTO toDTO(Permission permission) {
return PermissionDTO.builder()
.id(permission.getId())
.code(permission.getCode())
.name(permission.getName())
.type(permission.getType())
.resource(permission.getResource())
.description(permission.getDescription())
.createTime(permission.getCreateTime())
.updateTime(permission.getUpdateTime())
.build();
}
}
\ No newline at end of file
package com.fintech.penalty.service;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.entity.ReportTemplate;
import com.fintech.penalty.repository.ReportTemplateRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class ReportTemplateService {
private final ReportTemplateRepository templateRepository;
public PageResponse<ReportTemplateDTO> findAll(int page, int size) {
Page<ReportTemplate> pageResult = templateRepository.findAll(PageRequest.of(page, size));
List<ReportTemplateDTO> content = pageResult.getContent().stream()
.map(this::toDTO)
.collect(Collectors.toList());
return PageResponse.<ReportTemplateDTO>builder()
.content(content)
.totalElements(pageResult.getTotalElements())
.totalPages(pageResult.getTotalPages())
.pageNumber(page)
.pageSize(size)
.hasNext(pageResult.hasNext())
.hasPrevious(pageResult.hasPrevious())
.build();
}
public List<ReportTemplateDTO> findAllEnabled() {
return templateRepository.findByEnabledTrue().stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
public ReportTemplateDTO findById(Long id) {
return templateRepository.findById(id)
.map(this::toDTO)
.orElse(null);
}
public ReportTemplateDTO findDefault() {
return templateRepository.findByIsDefaultTrueAndEnabledTrue()
.map(this::toDTO)
.orElse(null);
}
@Transactional
public ReportTemplateDTO create(CreateReportTemplateRequest request) {
if (Boolean.TRUE.equals(request.getIsDefault())) {
clearDefaultTemplates();
}
ReportTemplate template = ReportTemplate.builder()
.name(request.getName())
.description(request.getDescription())
.content(request.getContent())
.isDefault(request.getIsDefault() != null ? request.getIsDefault() : false)
.enabled(request.getEnabled() != null ? request.getEnabled() : true)
.build();
ReportTemplate saved = templateRepository.save(template);
log.info("创建报告模板: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public ReportTemplateDTO update(Long id, UpdateReportTemplateRequest request) {
ReportTemplate template = templateRepository.findById(id)
.orElseThrow(() -> new RuntimeException("模板不存在"));
if (Boolean.TRUE.equals(request.getIsDefault()) && !Boolean.TRUE.equals(template.getIsDefault())) {
clearDefaultTemplates();
}
if (request.getName() != null) {
template.setName(request.getName());
}
if (request.getDescription() != null) {
template.setDescription(request.getDescription());
}
if (request.getContent() != null) {
template.setContent(request.getContent());
}
if (request.getIsDefault() != null) {
template.setIsDefault(request.getIsDefault());
}
if (request.getEnabled() != null) {
template.setEnabled(request.getEnabled());
}
ReportTemplate saved = templateRepository.save(template);
log.info("更新报告模板: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public void delete(Long id) {
ReportTemplate template = templateRepository.findById(id)
.orElseThrow(() -> new RuntimeException("模板不存在"));
templateRepository.delete(template);
log.info("删除报告模板: {}", template.getName());
}
private void clearDefaultTemplates() {
templateRepository.findByIsDefaultTrue().ifPresent(t -> {
t.setIsDefault(false);
templateRepository.save(t);
});
}
private ReportTemplateDTO toDTO(ReportTemplate template) {
return ReportTemplateDTO.builder()
.id(template.getId())
.name(template.getName())
.description(template.getDescription())
.content(template.getContent())
.isDefault(template.getIsDefault())
.enabled(template.getEnabled())
.createTime(template.getCreateTime())
.updateTime(template.getUpdateTime())
.build();
}
}
\ No newline at end of file
package com.fintech.penalty.service;
import com.fintech.penalty.dto.*;
import com.fintech.penalty.entity.Permission;
import com.fintech.penalty.entity.Role;
import com.fintech.penalty.entity.RoleMenu;
import com.fintech.penalty.entity.User;
import com.fintech.penalty.repository.MenuRepository;
import com.fintech.penalty.repository.PermissionRepository;
import com.fintech.penalty.repository.RoleMenuRepository;
import com.fintech.penalty.repository.RoleRepository;
import com.fintech.penalty.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class RoleService {
private final RoleRepository roleRepository;
private final PermissionRepository permissionRepository;
private final RoleMenuRepository roleMenuRepository;
private final MenuRepository menuRepository;
private final UserRepository userRepository;
public PageResponse<RoleDTO> findAll(int page, int size) {
Page<Role> pageResult = roleRepository.findAll(PageRequest.of(page, size));
List<RoleDTO> content = pageResult.getContent().stream()
.map(this::toDTO)
.collect(Collectors.toList());
return PageResponse.<RoleDTO>builder()
.content(content)
.totalElements(pageResult.getTotalElements())
.totalPages(pageResult.getTotalPages())
.pageNumber(page)
.pageSize(size)
.hasNext(pageResult.hasNext())
.hasPrevious(pageResult.hasPrevious())
.build();
}
public List<RoleDTO> findAllSimple() {
return roleRepository.findAll().stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
public RoleDTO findById(Long id) {
Role role = roleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("角色不存在"));
return toDTO(role);
}
@Transactional
public RoleDTO createRole(CreateRoleRequest request) {
if (roleRepository.existsByCode(request.getCode())) {
throw new RuntimeException("角色编码已存在");
}
Role role = Role.builder()
.code(request.getCode())
.name(request.getName())
.description(request.getDescription())
.build();
Role saved = roleRepository.save(role);
if (request.getPermissions() != null && !request.getPermissions().isEmpty()) {
List<Permission> permissions = permissionRepository.findByCodeIn(request.getPermissions());
List<RoleMenu> roleMenus = permissions.stream()
.map(p -> RoleMenu.builder().roleId(saved.getId()).menuId(Long.parseLong(p.getCode())).build())
.collect(Collectors.toList());
roleMenuRepository.saveAll(roleMenus);
}
log.info("创建角色: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public RoleDTO updateRole(Long id, UpdateRoleRequest request) {
Role role = roleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("角色不存在"));
if (request.getName() != null) {
role.setName(request.getName());
}
if (request.getDescription() != null) {
role.setDescription(request.getDescription());
}
Role saved = roleRepository.save(role);
if (request.getPermissions() != null) {
roleMenuRepository.deleteByRoleId(id);
List<Permission> permissions = permissionRepository.findByCodeIn(request.getPermissions());
List<RoleMenu> roleMenus = permissions.stream()
.map(p -> RoleMenu.builder().roleId(saved.getId()).menuId(Long.parseLong(p.getCode())).build())
.collect(Collectors.toList());
roleMenuRepository.saveAll(roleMenus);
}
log.info("更新角色: {}", saved.getName());
return toDTO(saved);
}
@Transactional
public void deleteRole(Long id) {
Role role = roleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("角色不存在"));
if ("ADMIN".equals(role.getCode()) || "USER".equals(role.getCode()) || "VIEWER".equals(role.getCode())) {
throw new RuntimeException("系统内置角色不能删除");
}
roleMenuRepository.deleteByRoleId(id);
roleRepository.delete(role);
log.info("删除角色: {}", role.getName());
}
public List<Long> getRoleMenuIds(Long roleId) {
return roleMenuRepository.findByRoleId(roleId).stream()
.map(RoleMenu::getMenuId)
.collect(Collectors.toList());
}
@Transactional
public void saveRoleMenus(Long roleId, List<Long> menuIds) {
roleMenuRepository.deleteByRoleId(roleId);
if (menuIds != null && !menuIds.isEmpty()) {
List<RoleMenu> roleMenus = menuIds.stream()
.map(menuId -> RoleMenu.builder().roleId(roleId).menuId(menuId).build())
.collect(Collectors.toList());
roleMenuRepository.saveAll(roleMenus);
}
log.info("更新角色 {} 的菜单权限", roleId);
}
public List<Long> getRoleUserIds(Long roleId) {
Role role = roleRepository.findById(roleId)
.orElseThrow(() -> new RuntimeException("角色不存在"));
String roleCode = role.getCode();
return userRepository.findAll().stream()
.filter(u -> roleCode.equals(u.getRoleCode()))
.map(User::getId)
.collect(Collectors.toList());
}
@Transactional
public void assignUserToRole(Long userId, String roleCode) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
Role role = roleRepository.findByCode(roleCode)
.orElseThrow(() -> new RuntimeException("角色不存在"));
user.setRoleCode(roleCode);
user.setRole(role);
userRepository.save(user);
log.info("用户 {} 分配角色 {}", user.getUsername(), roleCode);
}
@Transactional
public void removeUserFromRole(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
user.setRoleCode(null);
user.setRole(null);
userRepository.save(user);
log.info("用户 {} 移除角色", user.getUsername());
}
private RoleDTO toDTO(Role role) {
List<String> permissions = roleMenuRepository.findByRoleId(role.getId()).stream()
.map(rm -> {
return menuRepository.findById(rm.getMenuId())
.map(m -> m.getPath())
.orElse(null);
})
.filter(p -> p != null)
.collect(Collectors.toList());
return RoleDTO.builder()
.id(role.getId())
.code(role.getCode())
.name(role.getName())
.description(role.getDescription())
.permissions(permissions)
.createTime(role.getCreateTime())
.updateTime(role.getUpdateTime())
.build();
}
}
\ No newline at end of file
<template>
<div class="menu-manage">
<el-card>
<template #header>
<div class="card-header">
<span>菜单管理</span>
<el-button type="primary" :icon="Plus" @click="handleAdd(null)">添加菜单</el-button>
</div>
</template>
<el-table
:data="tableData"
v-loading="loading"
row-key="id"
:tree-props="{ children: 'children' }"
stripe
>
<el-table-column prop="name" label="菜单名称" width="200" />
<el-table-column prop="path" label="路径" />
<el-table-column prop="icon" label="图标" width="100">
<template #default="{ row }">
<el-icon v-if="row.icon"><component :is="row.icon" /></el-icon>
</template>
</el-table-column>
<el-table-column prop="sortOrder" label="排序" width="80" />
<el-table-column prop="visible" label="可见" width="80">
<template #default="{ row }">
<el-tag :type="row.visible ? 'success' : 'info'">
{{ row.visible ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="enabled" label="启用" width="80">
<template #default="{ row }">
<el-tag :type="row.enabled ? 'success' : 'danger'">
{{ row.enabled ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="300" fixed="right">
<template #default="{ row }">
<el-button type="primary" link :icon="Plus" @click="handleAdd(row)">添加子菜单</el-button>
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑菜单' : '添加菜单'"
width="500px"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="菜单名称" prop="name">
<el-input v-model="form.name" placeholder="请输入菜单名称" />
</el-form-item>
<el-form-item label="路径" prop="path">
<el-input v-model="form.path" placeholder="请输入路径" />
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input v-model="form.icon" placeholder="请输入图标名称" />
</el-form-item>
<el-form-item label="组件" prop="component">
<el-input v-model="form.component" placeholder="请输入组件名称" />
</el-form-item>
<el-form-item label="排序" prop="sortOrder">
<el-input-number v-model="form.sortOrder" :min="0" />
</el-form-item>
<el-form-item label="可见" prop="visible">
<el-switch v-model="form.visible" />
</el-form-item>
<el-form-item label="启用" prop="enabled">
<el-switch v-model="form.enabled" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
import api from '../api'
const tableData = ref([])
const loading = ref(false)
const dialogVisible = ref(false)
const isEdit = ref(false)
const submitLoading = ref(false)
const formRef = ref(null)
const form = reactive({
id: null,
name: '',
path: '',
icon: '',
component: '',
parentId: null,
sortOrder: 0,
visible: true,
enabled: true
})
const rules = {
name: [
{ required: true, message: '请输入菜单名称', trigger: 'blur' },
{ max: 100, message: '菜单名称长度不能超过100个字符', trigger: 'blur' }
]
}
const fetchData = async () => {
loading.value = true
try {
const res = await api.getMenus()
tableData.value = res.data
} catch (error) {
console.error('获取菜单列表失败:', error)
} finally {
loading.value = false
}
}
const handleAdd = (parent) => {
isEdit.value = false
Object.assign(form, {
id: null,
name: '',
path: '',
icon: '',
parentId: parent ? parent.id : null,
sortOrder: 0,
visible: true,
enabled: true,
component: ''
})
dialogVisible.value = true
}
const handleEdit = (row) => {
isEdit.value = true
Object.assign(form, {
id: row.id,
name: row.name,
path: row.path,
icon: row.icon,
component: row.component,
parentId: row.parentId,
sortOrder: row.sortOrder,
visible: row.visible,
enabled: row.enabled
})
dialogVisible.value = true
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
submitLoading.value = true
try {
if (isEdit.value) {
await api.updateMenu(form.id, form)
ElMessage.success('更新成功')
} else {
await api.createMenu(form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
} finally {
submitLoading.value = false
}
})
}
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm(`确定要删除菜单 ${row.name} 吗?`, '警告', {
type: 'warning'
})
await api.deleteMenu(row.id)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error)
}
}
}
onMounted(() => {
fetchData()
})
</script>
<style scoped lang="scss">
.menu-manage {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
\ No newline at end of file
<template>
<div class="permission-manage">
<el-card>
<template #header>
<div class="card-header">
<span>权限管理</span>
<el-button type="primary" :icon="Plus" @click="handleAdd">添加权限</el-button>
</div>
</template>
<el-table :data="tableData" v-loading="loading" stripe>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="code" label="权限编码" />
<el-table-column prop="name" label="权限名称" />
<el-table-column prop="type" label="类型" width="100">
<template #default="{ row }">
<el-tag>{{ row.type || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="resource" label="资源" />
<el-table-column prop="description" label="描述" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑权限' : '添加权限'"
width="500px"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="权限编码" prop="code" v-if="!isEdit">
<el-input v-model="form.code" placeholder="请输入权限编码" />
</el-form-item>
<el-form-item label="权限名称" prop="name">
<el-input v-model="form.name" placeholder="请输入权限名称" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="form.type" placeholder="请选择类型" style="width: 100%">
<el-option label="菜单" value="menu" />
<el-option label="按钮" value="button" />
<el-option label="接口" value="api" />
</el-select>
</el-form-item>
<el-form-item label="资源" prop="resource">
<el-input v-model="form.resource" placeholder="请输入资源路径" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入描述" :rows="3" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Edit, Delete } from '@element-plus/icons-vue'
import api from '../api'
const tableData = ref([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const dialogVisible = ref(false)
const isEdit = ref(false)
const submitLoading = ref(false)
const formRef = ref(null)
const form = reactive({
id: null,
code: '',
name: '',
type: 'menu',
resource: '',
description: ''
})
const rules = {
code: [
{ required: true, message: '请输入权限编码', trigger: 'blur' },
{ max: 100, message: '权限编码长度不能超过100个字符', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入权限名称', trigger: 'blur' },
{ max: 100, message: '权限名称长度不能超过100个字符', trigger: 'blur' }
]
}
const fetchData = async () => {
loading.value = true
try {
const res = await api.getAllPermissions({
page: currentPage.value - 1,
size: pageSize.value
})
tableData.value = res.data.content
total.value = res.data.totalElements
} catch (error) {
console.error('获取权限列表失败:', error)
} finally {
loading.value = false
}
}
const handleAdd = () => {
isEdit.value = false
Object.assign(form, {
id: null,
code: '',
name: '',
type: 'menu',
resource: '',
description: ''
})
dialogVisible.value = true
}
const handleEdit = (row) => {
isEdit.value = true
Object.assign(form, {
id: row.id,
code: row.code,
name: row.name,
type: row.type,
resource: row.resource,
description: row.description
})
dialogVisible.value = true
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
submitLoading.value = true
try {
if (isEdit.value) {
await api.updatePermission(form.id, {
name: form.name,
type: form.type,
resource: form.resource,
description: form.description
})
ElMessage.success('更新成功')
} else {
await api.createPermission(form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
} finally {
submitLoading.value = false
}
})
}
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm(`确定要删除权限 ${row.name} 吗?`, '警告', {
type: 'warning'
})
await api.deletePermission(row.id)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error)
}
}
}
const handleSizeChange = (val) => {
pageSize.value = val
fetchData()
}
const handleCurrentChange = (val) => {
currentPage.value = val
fetchData()
}
onMounted(() => {
fetchData()
})
</script>
<style scoped lang="scss">
.permission-manage {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
</style>
\ No newline at end of file
<template>
<div class="report-config">
<el-card>
<el-tabs v-model="activeTab" @tab-change="handleTabChange">
<el-tab-pane label="报告模板" name="templates">
<div class="toolbar">
<el-button type="primary" :icon="Plus" @click="handleAddTemplate">添加模板</el-button>
</div>
<el-table :data="templateData" v-loading="templateLoading" stripe>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="模板名称" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="isDefault" label="默认" width="80">
<template #default="{ row }">
<el-tag :type="row.isDefault ? 'success' : 'info'">
{{ row.isDefault ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="enabled" label="启用" width="80">
<template #default="{ row }">
<el-tag :type="row.enabled ? 'success' : 'danger'">
{{ row.enabled ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-button type="primary" link @click="handleEditTemplate(row)">编辑</el-button>
<el-button type="success" link @click="handleSetDefault(row)" :disabled="row.isDefault">设为默认</el-button>
<el-button type="danger" link @click="handleDeleteTemplate(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="分析规则" name="rules">
<div class="toolbar">
<el-button type="primary" :icon="Plus" @click="handleAddRule">添加规则</el-button>
</div>
<el-table :data="ruleData" v-loading="ruleLoading" stripe>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="规则名称" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="institutionType" label="机构类型" width="100" />
<el-table-column prop="penaltyType" label="处罚类型" width="100" />
<el-table-column prop="priority" label="优先级" width="80" />
<el-table-column prop="enabled" label="启用" width="80">
<template #default="{ row }">
<el-tag :type="row.enabled ? 'success' : 'danger'">
{{ row.enabled ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button type="primary" link @click="handleEditRule(row)">编辑</el-button>
<el-button type="danger" link @click="handleDeleteRule(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="关键词" name="keywords">
<div class="toolbar">
<el-button type="primary" :icon="Plus" @click="handleAddKeyword">添加关键词</el-button>
</div>
<el-table :data="keywordData" v-loading="keywordLoading" stripe>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="keyword" label="关键词" />
<el-table-column prop="category" label="分类" width="120" />
<el-table-column prop="level" label="级别" width="100">
<template #default="{ row }">
<el-tag :type="getLevelType(row.level)">{{ row.level || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" />
<el-table-column prop="enabled" label="启用" width="80">
<template #default="{ row }">
<el-tag :type="row.enabled ? 'success' : 'danger'">
{{ row.enabled ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button type="primary" link @click="handleEditKeyword(row)">编辑</el-button>
<el-button type="danger" link @click="handleDeleteKeyword(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="当前配置" name="active">
<div class="active-config">
<el-alert title="设置当前分析使用的配置,保存后系统将使用此配置进行分析" type="info" :closable="false" show-icon style="margin-bottom: 20px"/>
<el-row :gutter="20">
<el-col :span="8">
<el-card class="config-card">
<template #header>
<div class="card-header">
<span class="card-title">选择模板</span>
</div>
</template>
<el-select v-model="selectedTemplateId" placeholder="请选择模板" style="width: 100%" @change="handleConfigChange">
<el-option v-for="t in templateData" :key="t.id" :label="t.name" :value="t.id">
<span>{{ t.name }}</span>
<el-tag size="small" :type="t.isDefault ? 'success' : 'info'" style="margin-left: 8px">{{ t.isDefault ? '默认' : '' }}</el-tag>
</el-option>
</el-select>
<div v-if="selectedTemplateId" class="selected-info">
<div class="config-item"><strong>名称:</strong>{{ getTemplateName(selectedTemplateId) }}</div>
</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="config-card">
<template #header>
<div class="card-header">
<span class="card-title">选择规则</span>
<el-button type="primary" link @click="selectAllRules" size="small">全选</el-button>
</div>
</template>
<el-checkbox-group v-model="selectedRuleIds" @change="handleConfigChange">
<el-checkbox v-for="r in ruleData" :key="r.id" :label="r.id" class="rule-checkbox">
{{ r.name }}
<span class="rule-meta">({{ r.institutionType || '不限' }}/{{ r.penaltyType || '不限' }})</span>
</el-checkbox>
</el-checkbox-group>
<div v-if="selectedRuleIds.length === 0" class="empty-tip">请选择规则</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="config-card">
<template #header>
<div class="card-header">
<span class="card-title">选择关键词</span>
<el-button type="primary" link @click="selectAllKeywords" size="small">全选</el-button>
</div>
</template>
<el-checkbox-group v-model="selectedKeywordIds" @change="handleConfigChange">
<el-checkbox v-for="k in keywordData" :key="k.id" :label="k.id" class="keyword-checkbox">
{{ k.keyword }}
<el-tag size="small" :type="getLevelType(k.level)" style="margin-left: 4px">{{ k.level || '' }}</el-tag>
</el-checkbox>
</el-checkbox-group>
<div v-if="selectedKeywordIds.length === 0" class="empty-tip">请选择关键词</div>
</el-card>
</el-col>
</el-row>
<div class="save-bar">
<el-button type="primary" :loading="savingConfig" @click="handleSaveConfig">保存配置</el-button>
<span class="save-tip" v-if="savedConfig">已保存于 {{ savedConfig }}</span>
</div>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
<el-dialog v-model="templateDialogVisible" :title="templateIsEdit ? '编辑模板' : '添加模板'" width="700px">
<el-form ref="templateFormRef" :model="templateForm" :rules="templateRules" label-width="100px">
<el-form-item label="模板名称" prop="name">
<el-input v-model="templateForm.name" placeholder="请输入模板名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="templateForm.description" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="模板内容" prop="content">
<el-input v-model="templateForm.content" type="textarea" :rows="15" placeholder="请输入模板内容,使用{institutionName}等占位符" />
</el-form-item>
<el-form-item label="设为默认">
<el-switch v-model="templateForm.isDefault" />
</el-form-item>
<el-form-item label="启用">
<el-switch v-model="templateForm.enabled" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="templateDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmitTemplate" :loading="templateSubmitLoading">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="ruleDialogVisible" :title="ruleIsEdit ? '编辑规则' : '添加规则'" width="600px">
<el-form ref="ruleFormRef" :model="ruleForm" :rules="ruleRules" label-width="100px">
<el-form-item label="规则名称" prop="name">
<el-input v-model="ruleForm.name" placeholder="请输入规则名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="ruleForm.description" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="机构类型">
<el-select v-model="ruleForm.institutionType" placeholder="请选择" clearable style="width: 100%">
<el-option label="银行" value="银行" />
<el-option label="保险" value="保险" />
<el-option label="证券" value="证券" />
<el-option label="基金" value="基金" />
<el-option label="其他" value="其他" />
</el-select>
</el-form-item>
<el-form-item label="处罚类型">
<el-select v-model="ruleForm.penaltyType" placeholder="请选择" clearable style="width: 100%">
<el-option label="罚款" value="罚款" />
<el-option label="警告" value="警告" />
<el-option label="没收违法所得" value="没收违法所得" />
<el-option label="停业" value="停业" />
<el-option label="吊销许可证" value="吊销许可证" />
<el-option label="市场禁入" value="市场禁入" />
</el-select>
</el-form-item>
<el-form-item label="最小金额">
<el-input-number v-model="ruleForm.minAmount" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="最大金额">
<el-input-number v-model="ruleForm.maxAmount" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="分析提示词" prop="prompt">
<el-input v-model="ruleForm.prompt" type="textarea" :rows="4" placeholder="请输入分析提示词" />
</el-form-item>
<el-form-item label="优先级">
<el-input-number v-model="ruleForm.priority" :min="0" :max="100" style="width: 100%" />
</el-form-item>
<el-form-item label="启用">
<el-switch v-model="ruleForm.enabled" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="ruleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmitRule" :loading="ruleSubmitLoading">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="keywordDialogVisible" :title="keywordIsEdit ? '编辑关键词' : '添加关键词'" width="500px">
<el-form ref="keywordFormRef" :model="keywordForm" :rules="keywordRules" label-width="80px">
<el-form-item label="关键词" prop="keyword">
<el-input v-model="keywordForm.keyword" placeholder="请输入关键词" />
</el-form-item>
<el-form-item label="分类">
<el-select v-model="keywordForm.category" placeholder="请选择" clearable style="width: 100%">
<el-option label="违规类型" value="违规类型" />
<el-option label="处罚类型" value="处罚类型" />
<el-option label="行业领域" value="行业领域" />
</el-select>
</el-form-item>
<el-form-item label="级别">
<el-select v-model="keywordForm.level" placeholder="请选择" clearable style="width: 100%">
<el-option label="严重" value="严重" />
<el-option label="高" value="高" />
<el-option label="中" value="中" />
<el-option label="低" value="低" />
</el-select>
</el-form-item>
<el-form-item label="描述">
<el-input v-model="keywordForm.description" type="textarea" :rows="2" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="启用">
<el-switch v-model="keywordForm.enabled" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="keywordDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmitKeyword" :loading="keywordSubmitLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import api from '../api'
const activeTab = ref('templates')
const templateData = ref([])
const templateLoading = ref(false)
const templateDialogVisible = ref(false)
const templateIsEdit = ref(false)
const templateSubmitLoading = ref(false)
const templateFormRef = ref(null)
const templateForm = reactive({
id: null,
name: '',
description: '',
content: '',
isDefault: false,
enabled: true
})
const templateRules = {
name: [{ required: true, message: '请输入模板名称', trigger: 'blur' }],
content: [{ required: true, message: '请输入模板内容', trigger: 'blur' }]
}
const ruleData = ref([])
const ruleLoading = ref(false)
const ruleDialogVisible = ref(false)
const ruleIsEdit = ref(false)
const ruleSubmitLoading = ref(false)
const ruleFormRef = ref(null)
const ruleForm = reactive({
id: null,
name: '',
description: '',
institutionType: null,
penaltyType: null,
minAmount: null,
maxAmount: null,
prompt: '',
priority: 0,
enabled: true
})
const ruleRules = {
name: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
prompt: [{ required: true, message: '请输入分析提示词', trigger: 'blur' }]
}
const keywordData = ref([])
const keywordLoading = ref(false)
const keywordDialogVisible = ref(false)
const keywordIsEdit = ref(false)
const keywordSubmitLoading = ref(false)
const keywordFormRef = ref(null)
const keywordForm = reactive({
id: null,
keyword: '',
category: null,
level: null,
description: '',
enabled: true
})
const keywordRules = {
keyword: [{ required: true, message: '请输入关键词', trigger: 'blur' }]
}
const selectedTemplateId = ref(null)
const selectedRuleIds = ref([])
const selectedKeywordIds = ref([])
const savingConfig = ref(false)
const savedConfig = ref('')
const getLevelType = (level) => {
const map = { '严重': 'danger', '高': 'warning', '中': 'info', '低': 'success' }
return map[level] || 'info'
}
const fetchTemplates = async () => {
templateLoading.value = true
try {
const res = await api.getReportTemplates({ page: 0, size: 100 })
templateData.value = res.data.content
} catch (error) {
console.error('获取模板失败:', error)
} finally {
templateLoading.value = false
}
}
const fetchRules = async () => {
ruleLoading.value = true
try {
const res = await api.getAnalysisRules({ page: 0, size: 100 })
ruleData.value = res.data.content
} catch (error) {
console.error('获取规则失败:', error)
} finally {
ruleLoading.value = false
}
}
const fetchKeywords = async () => {
keywordLoading.value = true
try {
const res = await api.getAnalysisKeywords({ page: 0, size: 100 })
keywordData.value = res.data.content
} catch (error) {
console.error('获取关键词失败:', error)
} finally {
keywordLoading.value = false
}
}
const fetchActiveConfig = async () => {
try {
const res = await api.getAnalysisConfig()
const config = res.data.data
if (config) {
selectedTemplateId.value = config.templateId
selectedRuleIds.value = config.ruleIds || []
selectedKeywordIds.value = config.keywordIds || []
savedConfig.value = config.updatedAt || ''
}
} catch (error) {
console.error('获取当前配置失败:', error)
}
}
const getTemplateName = (id) => {
const t = templateData.value.find(x => x.id === id)
return t ? t.name : ''
}
const handleConfigChange = () => {
savedConfig.value = ''
}
const selectAllRules = () => {
selectedRuleIds.value = ruleData.value.map(r => r.id)
savedConfig.value = ''
}
const selectAllKeywords = () => {
selectedKeywordIds.value = keywordData.value.map(k => k.id)
savedConfig.value = ''
}
const handleSaveConfig = async () => {
if (!selectedTemplateId.value) {
ElMessage.warning('请选择模板')
return
}
savingConfig.value = true
try {
await api.setAnalysisConfig({
templateId: selectedTemplateId.value,
ruleIds: selectedRuleIds.value,
keywordIds: selectedKeywordIds.value
})
ElMessage.success('配置保存成功')
savedConfig.value = new Date().toLocaleString()
} catch (error) {
ElMessage.error('保存失败')
} finally {
savingConfig.value = false
}
}
onMounted(() => {
fetchTemplates()
fetchRules()
fetchKeywords()
fetchActiveConfig()
})
const handleTabChange = (tabName) => {
if (tabName === 'active') {
fetchActiveConfig()
}
}
const handleAddTemplate = () => {
templateIsEdit.value = false
Object.assign(templateForm, { id: null, name: '', description: '', content: '', isDefault: false, enabled: true })
templateDialogVisible.value = true
}
const handleEditTemplate = (row) => {
templateIsEdit.value = true
Object.assign(templateForm, row)
templateDialogVisible.value = true
}
const handleSubmitTemplate = async () => {
if (!templateFormRef.value) return
await templateFormRef.value.validate(async (valid) => {
if (!valid) return
templateSubmitLoading.value = true
try {
if (templateIsEdit.value) {
await api.updateReportTemplate(templateForm.id, templateForm)
ElMessage.success('更新成功')
} else {
await api.createReportTemplate(templateForm)
ElMessage.success('创建成功')
}
templateDialogVisible.value = false
fetchTemplates()
} catch (error) {
} finally {
templateSubmitLoading.value = false
}
})
}
const handleSetDefault = async (row) => {
try {
await api.updateReportTemplate(row.id, { isDefault: true })
ElMessage.success('设置成功')
fetchTemplates()
} catch (error) {}
}
const handleDeleteTemplate = async (row) => {
try {
await ElMessageBox.confirm(`确定要删除模板 ${row.name} 吗?`, '警告', { type: 'warning' })
await api.deleteReportTemplate(row.id)
ElMessage.success('删除成功')
fetchTemplates()
} catch (error) {
if (error !== 'cancel') console.error('删除失败:', error)
}
}
const handleAddRule = () => {
ruleIsEdit.value = false
Object.assign(ruleForm, { id: null, name: '', description: '', institutionType: null, penaltyType: null, minAmount: null, maxAmount: null, prompt: '', priority: 0, enabled: true })
ruleDialogVisible.value = true
}
const handleEditRule = (row) => {
ruleIsEdit.value = true
Object.assign(ruleForm, row)
ruleDialogVisible.value = true
}
const handleSubmitRule = async () => {
if (!ruleFormRef.value) return
await ruleFormRef.value.validate(async (valid) => {
if (!valid) return
ruleSubmitLoading.value = true
try {
if (ruleIsEdit.value) {
await api.updateAnalysisRule(ruleForm.id, ruleForm)
ElMessage.success('更新成功')
} else {
await api.createAnalysisRule(ruleForm)
ElMessage.success('创建成功')
}
ruleDialogVisible.value = false
fetchRules()
} catch (error) {
} finally {
ruleSubmitLoading.value = false
}
})
}
const handleDeleteRule = async (row) => {
try {
await ElMessageBox.confirm(`确定要删除规则 ${row.name} 吗?`, '警告', { type: 'warning' })
await api.deleteAnalysisRule(row.id)
ElMessage.success('删除成功')
fetchRules()
} catch (error) {
if (error !== 'cancel') console.error('删除失败:', error)
}
}
const handleAddKeyword = () => {
keywordIsEdit.value = false
Object.assign(keywordForm, { id: null, keyword: '', category: null, level: null, description: '', enabled: true })
keywordDialogVisible.value = true
}
const handleEditKeyword = (row) => {
keywordIsEdit.value = true
Object.assign(keywordForm, row)
keywordDialogVisible.value = true
}
const handleSubmitKeyword = async () => {
if (!keywordFormRef.value) return
await keywordFormRef.value.validate(async (valid) => {
if (!valid) return
keywordSubmitLoading.value = true
try {
if (keywordIsEdit.value) {
await api.updateAnalysisKeyword(keywordForm.id, keywordForm)
ElMessage.success('更新成功')
} else {
await api.createAnalysisKeyword(keywordForm)
ElMessage.success('创建成功')
}
keywordDialogVisible.value = false
fetchKeywords()
} catch (error) {
} finally {
keywordSubmitLoading.value = false
}
})
}
const handleDeleteKeyword = async (row) => {
try {
await ElMessageBox.confirm(`确定要删除关键词 ${row.keyword} 吗?`, '警告', { type: 'warning' })
await api.deleteAnalysisKeyword(row.id)
ElMessage.success('删除成功')
fetchKeywords()
} catch (error) {
if (error !== 'cancel') console.error('删除失败:', error)
}
}
</script>
<style scoped lang="scss">
.report-config {
padding: 20px;
}
.toolbar {
margin-bottom: 16px;
}
.active-config {
padding: 10px 0;
}
.config-card {
margin-bottom: 20px;
min-height: 200px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title {
font-weight: 600;
font-size: 15px;
}
}
.config-content {
.config-item {
margin-bottom: 12px;
font-size: 14px;
line-height: 1.6;
word-break: break-all;
}
}
.config-list {
.config-list-item {
padding: 10px;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
.rule-name {
font-weight: 500;
margin-bottom: 6px;
}
.rule-meta {
font-size: 12px;
color: #909399;
span {
margin-right: 12px;
}
}
}
}
.keyword-tags {
.keyword-tag {
margin: 4px;
}
}
.selected-info {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #eee;
}
.rule-checkbox, .keyword-checkbox {
display: block;
margin: 8px 0;
.rule-meta {
font-size: 12px;
color: #909399;
}
}
.empty-tip {
color: #909399;
font-size: 14px;
text-align: center;
padding: 20px 0;
}
.save-bar {
margin-top: 20px;
display: flex;
align-items: center;
gap: 16px;
.save-tip {
color: #909399;
font-size: 14px;
}
}
</style>
\ No newline at end of file
<template>
<div class="role-manage">
<el-card>
<template #header>
<div class="card-header">
<span>角色管理</span>
<el-button type="primary" :icon="Plus" @click="handleAdd">添加角色</el-button>
</div>
</template>
<el-table :data="tableData" v-loading="loading" stripe>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="code" label="角色编码" />
<el-table-column prop="name" label="角色名称" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="createTime" label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="350" fixed="right">
<template #default="{ row }">
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button type="success" link :icon="Key" @click="handleAuth(row)">授权</el-button>
<el-button type="warning" link :icon="User" @click="handleUsers(row)">用户</el-button>
<el-button
type="danger"
link
:icon="Delete"
@click="handleDelete(row)"
:disabled="['ADMIN', 'USER', 'VIEWER'].includes(row.code)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑角色' : '添加角色'"
width="500px"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="角色编码" prop="code" v-if="!isEdit">
<el-input v-model="form.code" placeholder="请输入角色编码" />
</el-form-item>
<el-form-item label="角色名称" prop="name">
<el-input v-model="form.name" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入描述" :rows="3" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</template>
</el-dialog>
<el-drawer
v-model="authDrawerVisible"
:title="`菜单授权 - ${currentRole?.name || ''}`"
direction="rtl"
size="400px"
>
<div class="menu-tree-container">
<el-tree
ref="menuTreeRef"
:data="menuData"
:props="{ children: 'children', label: 'name' }"
show-checkbox
node-key="id"
:default-checked-keys="checkedMenuIds"
:default-expanded-keys="expandedMenuIds"
/>
</div>
<template #footer>
<el-button @click="authDrawerVisible = false">取消</el-button>
<el-button type="primary" @click="saveRoleMenus" :loading="authLoading">保存</el-button>
</template>
</el-drawer>
<el-drawer
v-model="userDrawerVisible"
:title="`用户分配 - ${currentRole?.name || ''}`"
direction="rtl"
size="40%"
>
<div class="user-assign-container">
<div class="user-panel">
<div class="panel-header">未分配用户</div>
<div class="user-list">
<div
v-for="user in unassignedUsers"
:key="user.id"
class="user-item"
@click="assignUser(user)"
>
<el-avatar :size="32" :icon="User" />
<div class="user-info">
<div class="username">{{ user.username }}</div>
<div class="nickname">{{ user.nickname || '-' }}</div>
</div>
<el-icon class="add-icon"><Plus /></el-icon>
</div>
</div>
</div>
<div class="divider">
<el-icon><DArrowLeft /></el-icon>
<el-icon><DArrowRight /></el-icon>
</div>
<div class="user-panel">
<div class="panel-header">已分配用户</div>
<div class="user-list">
<div
v-for="user in assignedUsersList"
:key="user.id"
class="user-item assigned"
@click="removeUser(user)"
>
<el-avatar :size="32" :icon="User" />
<div class="user-info">
<div class="username">{{ user.username }}</div>
<div class="nickname">{{ user.nickname || '-' }}</div>
</div>
<el-icon class="remove-icon"><Close /></el-icon>
</div>
</div>
</div>
</div>
</el-drawer>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Edit, Delete, Key, User, Close, DArrowLeft, DArrowRight } from '@element-plus/icons-vue'
import api from '../api'
const tableData = ref([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const dialogVisible = ref(false)
const isEdit = ref(false)
const submitLoading = ref(false)
const formRef = ref(null)
const form = reactive({
id: null,
code: '',
name: '',
description: ''
})
const rules = {
code: [
{ required: true, message: '请输入角色编码', trigger: 'blur' },
{ min: 2, max: 50, message: '角色编码长度在2-50个字符', trigger: 'blur' }
],
name: [
{ required: true, message: '请输入角色名称', trigger: 'blur' },
{ max: 100, message: '角色名称长度不能超过100个字符', trigger: 'blur' }
]
}
// 授权相关
const authDrawerVisible = ref(false)
const currentRole = ref(null)
const menuData = ref([])
const checkedMenuIds = ref([])
const expandedMenuIds = ref([])
const menuTreeRef = ref(null)
const authLoading = ref(false)
const fetchData = async () => {
loading.value = true
try {
const res = await api.getAllRoles({
page: currentPage.value - 1,
size: pageSize.value
})
tableData.value = res.data.content
total.value = res.data.totalElements
} catch (error) {
console.error('获取角色列表失败:', error)
} finally {
loading.value = false
}
}
const handleAdd = () => {
isEdit.value = false
Object.assign(form, {
id: null,
code: '',
name: '',
description: ''
})
dialogVisible.value = true
}
const handleEdit = (row) => {
isEdit.value = true
Object.assign(form, {
id: row.id,
code: row.code,
name: row.name,
description: row.description
})
dialogVisible.value = true
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
submitLoading.value = true
try {
if (isEdit.value) {
await api.updateRole(form.id, {
name: form.name,
description: form.description
})
ElMessage.success('更新成功')
} else {
await api.createRole(form)
ElMessage.success('创建成功')
}
dialogVisible.value = false
fetchData()
} catch (error) {
} finally {
submitLoading.value = false
}
})
}
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm(`确定要删除角色 ${row.name} 吗?`, '警告', {
type: 'warning'
})
await api.deleteRole(row.id)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error)
}
}
}
const handleSizeChange = (val) => {
pageSize.value = val
fetchData()
}
const handleCurrentChange = (val) => {
currentPage.value = val
fetchData()
}
const formatDate = (dateStr) => {
if (!dateStr) return '-'
return dateStr.replace('T', ' ').substring(0, 19)
}
const handleAuth = async (row) => {
currentRole.value = row
authDrawerVisible.value = true
try {
const [menusRes, roleMenusRes] = await Promise.all([
api.getMenus(),
api.getRoleMenus(row.id)
])
menuData.value = menusRes.data || []
checkedMenuIds.value = roleMenusRes.data || []
// 获取所有菜单ID用于展开
const getAllIds = (list) => {
let ids = []
list.forEach(item => {
ids.push(item.id)
if (item.children && item.children.length) {
ids = ids.concat(getAllIds(item.children))
}
})
return ids
}
expandedMenuIds.value = getAllIds(menuData.value)
} catch (error) {
console.error('获取菜单失败:', error)
}
}
const saveRoleMenus = async () => {
if (!currentRole.value) return
authLoading.value = true
try {
const checkedKeys = menuTreeRef.value.getCheckedKeys()
const halfCheckedKeys = menuTreeRef.value.getHalfCheckedKeys()
const allKeys = [...checkedKeys, ...halfCheckedKeys]
await api.saveRoleMenus(currentRole.value.id, allKeys)
ElMessage.success('保存成功')
authDrawerVisible.value = false
} catch (error) {
console.error('保存失败:', error)
} finally {
authLoading.value = false
}
}
// 用户分配相关
const userDrawerVisible = ref(false)
const allUsersList = ref([])
const assignedUserIds = ref([])
const unassignedUsers = computed(() => {
return allUsersList.value.filter(u => !assignedUserIds.value.includes(u.id))
})
const assignedUsersList = computed(() => {
return allUsersList.value.filter(u => assignedUserIds.value.includes(u.id))
})
const handleUsers = async (row) => {
currentRole.value = row
userDrawerVisible.value = true
try {
const [usersRes, roleUsersRes] = await Promise.all([
api.getUsers({ page: 0, size: 1000 }),
api.getRoleUsers(row.id)
])
allUsersList.value = usersRes.data.content || []
assignedUserIds.value = roleUsersRes.data || []
} catch (error) {
console.error('获取用户失败:', error)
}
}
const assignUser = async (user) => {
if (!currentRole.value) return
try {
await api.assignUserToRole({ userId: user.id, roleCode: currentRole.value.code })
assignedUserIds.value.push(user.id)
ElMessage.success('分配成功')
} catch (error) {
console.error('分配失败:', error)
}
}
const removeUser = async (user) => {
if (!currentRole.value) return
try {
await api.removeUserFromRole(user.id)
assignedUserIds.value = assignedUserIds.value.filter(id => id !== user.id)
ElMessage.success('移除成功')
} catch (error) {
console.error('移除失败:', error)
}
}
onMounted(() => {
fetchData()
})
</script>
<style scoped lang="scss">
.role-manage {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.pagination {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.menu-tree-container {
height: calc(100vh - 200px);
overflow-y: auto;
}
.user-assign-container {
display: flex;
height: calc(100vh - 150px);
gap: 10px;
}
.user-panel {
flex: 1;
display: flex;
flex-direction: column;
border: 1px solid #eee;
border-radius: 4px;
overflow: hidden;
.panel-header {
padding: 12px;
background: #f5f7fa;
font-weight: 600;
text-align: center;
border-bottom: 1px solid #eee;
}
.user-list {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.user-item {
display: flex;
align-items: center;
padding: 10px;
margin-bottom: 8px;
border: 1px solid #eee;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: #f0f9ff;
border-color: #409eff;
.add-icon {
color: #409eff;
}
}
&.assigned:hover {
background: #fff0f0;
border-color: #f56c6c;
.remove-icon {
color: #f56c6c;
}
}
.user-info {
flex: 1;
margin-left: 10px;
.username {
font-weight: 500;
}
.nickname {
font-size: 12px;
color: #999;
}
}
.add-icon, .remove-icon {
color: #ccc;
}
}
}
.divider {
display: flex;
flex-direction: column;
justify-content: center;
gap: 20px;
color: #999;
}
</style>
\ No newline at end of file
<template>
<div class="role-menu-manage">
<el-card>
<template #header>
<span>角色菜单权限</span>
</template>
<el-row :gutter="20">
<el-col :span="8">
<h4>角色列表</h4>
<el-table :data="roles" border @row-click="selectRole" :highlight-current-row="true">
<el-table-column prop="name" label="角色名称" />
<el-table-column prop="code" label="编码" />
</el-table>
</el-col>
<el-col :span="16">
<h4>菜单权限分配</h4>
<div v-if="selectedRole" class="menu-tree-container">
<el-tree
ref="menuTreeRef"
:data="menus"
:props="{ children: 'children', label: 'name' }"
show-checkbox
node-key="id"
:default-checked-keys="checkedMenuIds"
:default-expanded-keys="expandedMenuIds"
/>
<div class="action-buttons">
<el-button type="primary" @click="saveRoleMenus">保存分配</el-button>
</div>
</div>
<el-empty v-else description="请先选择左侧角色" />
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import api from '../api'
const roles = ref([])
const menus = ref([])
const selectedRole = ref(null)
const menuTreeRef = ref(null)
const checkedMenuIds = ref([])
const expandedMenuIds = ref([])
const fetchRoles = async () => {
try {
const res = await api.getRolesSimple()
roles.value = res.data
console.log('Roles loaded:', roles.value)
} catch (error) {
console.error('获取角色失败:', error)
}
}
const fetchMenus = async () => {
try {
const res = await api.getMenus()
menus.value = res.data
console.log('Menus loaded:', menus.value)
// Get all menu IDs for expansion
const getAllIds = (menuList) => {
let ids = []
menuList.forEach(m => {
ids.push(m.id)
if (m.children && m.children.length) {
ids = ids.concat(getAllIds(m.children))
}
})
return ids
}
expandedMenuIds.value = getAllIds(menus.value)
} catch (error) {
console.error('获取菜单失败:', error)
}
}
const selectRole = async (row) => {
selectedRole.value = row
await fetchRoleMenus(row.id)
}
const fetchRoleMenus = async (roleId) => {
try {
const res = await api.getRoleMenus(roleId)
checkedMenuIds.value = res.data || []
} catch (error) {
console.error('获取角色菜单失败:', error)
checkedMenuIds.value = []
}
}
const saveRoleMenus = async () => {
if (!selectedRole.value) return
const checkedKeys = menuTreeRef.value.getCheckedKeys()
const halfCheckedKeys = menuTreeRef.value.getHalfCheckedKeys()
const allKeys = [...checkedKeys, ...halfCheckedKeys]
try {
await api.saveRoleMenus(selectedRole.value.id, allKeys)
ElMessage.success('保存成功')
} catch (error) {
console.error('保存失败:', error)
}
}
onMounted(() => {
fetchRoles()
fetchMenus()
})
</script>
<style scoped lang="scss">
.role-menu-manage {
padding: 20px;
}
.menu-tree-container {
max-height: 500px;
overflow-y: auto;
}
.action-buttons {
margin-top: 20px;
text-align: right;
}
h4 {
margin-bottom: 16px;
font-weight: 600;
}
</style>
\ No newline at end of file
<template>
<div class="role-user-manage">
<el-card>
<template #header>
<span>角色用户分配</span>
</template>
<el-row :gutter="20">
<el-col :span="8">
<h4>角色列表</h4>
<el-table :data="roles" border @row-click="selectRole" :highlight-current-row="true">
<el-table-column prop="name" label="角色名称" />
<el-table-column prop="code" label="编码" />
<el-table-column label="用户数" width="80">
<template #default="{ row }">
{{ getUserCount(row.id) }}
</template>
</el-table-column>
</el-table>
</el-col>
<el-col :span="16">
<h4>用户分配</h4>
<div v-if="selectedRole" class="user-transfer-container">
<el-transfer
v-model="assignedUserIds"
:data="allUsers"
:titles="['未分配用户', '已分配用户']"
@change="handleUserChange"
/>
</div>
<el-empty v-else description="请先选择左侧角色" />
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import api from '../api'
const roles = ref([])
const allUsers = ref([])
const selectedRole = ref(null)
const assignedUserIds = ref([])
const roleUserMap = ref({})
const roleUserCount = computed(() => {
const counts = {}
Object.values(roleUserMap.value).forEach(userIds => {
userIds.forEach(uid => {
counts[uid] = (counts[uid] || 0) + 1
})
})
return counts
})
const fetchRoles = async () => {
try {
const res = await api.getRolesSimple()
roles.value = res.data
console.log('Roles:', roles.value)
} catch (error) {
console.error('获取角色失败:', error)
}
}
const fetchUsers = async () => {
try {
const res = await api.getUsers({ page: 0, size: 1000 })
allUsers.value = res.data.content.map(u => ({
key: u.id,
label: `${u.username} (${u.nickname || '-' })`
}))
console.log('Users:', allUsers.value.length)
} catch (error) {
console.error('获取用户失败:', error)
}
}
const fetchRoleUsers = async (roleId) => {
try {
const res = await api.getRoleUsers(roleId)
assignedUserIds.value = res.data || []
roleUserMap.value[roleId] = assignedUserIds.value
} catch (error) {
console.error('获取角色用户失败:', error)
assignedUserIds.value = []
}
}
const selectRole = async (row) => {
selectedRole.value = row
await fetchRoleUsers(row.id)
}
const getUserCount = (roleId) => {
return roleUserMap.value[roleId]?.length || 0
}
const handleUserChange = async (value, direction, movedKeys) => {
if (!selectedRole.value) return
try {
if (direction === 'right') {
for (const userId of movedKeys) {
await api.assignUserToRole({ userId, roleCode: selectedRole.value.code })
}
} else {
for (const userId of movedKeys) {
await api.removeUserFromRole(userId)
}
}
ElMessage.success('更新成功')
roleUserMap.value[selectedRole.value.id] = value
} catch (error) {
console.error('更新失败:', error)
await fetchRoleUsers(selectedRole.value.id)
}
}
onMounted(() => {
fetchRoles()
fetchUsers()
})
</script>
<style scoped lang="scss">
.role-user-manage {
padding: 20px;
}
.user-transfer-container {
max-height: 500px;
}
h4 {
margin-bottom: 16px;
font-weight: 600;
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论