feat: 实现资金平台核心功能模块

## 新增功能

### Gateway增强 (P0)
- GlobalLogFilter: 全局日志过滤器,生成TraceId
- JwtAuthFilter: JWT鉴权过滤器,白名单机制
- RateLimitConfig: 基于Redis的限流配置

### 权限管理模块 (P1/P3)
- A.1 用户管理: UserDTO/UserVO/UserService/UserController
- A.2 角色管理: RoleDTO/RoleVO/RoleService/RoleController
- A.3 菜单管理: MenuDTO/MenuVO/MenuService/MenuController
- A.4 部门管理: DeptDTO/DeptVO/DeptService/DeptController

### 业务模块 (P1/P3)
- B.1 fund-req用款申请: 完整审批流程
- B.2 fund-exp支出管理: 支付确认流程
- B.3 fund-receipt收款管理: 确认核销流程

### 服务治理 (P1/P2)
- D.1 Nacos服务注册: 所有服务集成Nacos
- I.1 Redis缓存: RedisService/RedisConfig
- E.1 ELK日志: logback-spring.xml JSON格式

## 技术栈
- Spring Cloud Gateway 4.0.0
- Spring Cloud Alibaba 2023.0.0
- MyBatis-Plus 3.5.5
- JWT (jjwt 0.11.5)
- Redis + Lettuce
This commit is contained in:
zhangjf 2026-02-17 14:30:04 +08:00
parent a17307a96e
commit 281bbc992d
74 changed files with 5616 additions and 0 deletions

View File

@ -37,6 +37,18 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Jackson for Redis serialization -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,276 @@
package com.fundplatform.common.cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Redis缓存服务
*/
@Service
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
public RedisService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// ==================== String操作 ====================
/**
* 设置缓存
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 设置缓存并设置过期时间
*/
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 设置缓存并设置过期时间
*/
public void setEx(String key, Object value, long seconds) {
redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
}
/**
* 获取缓存
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 获取缓存并转换为指定类型
*/
@SuppressWarnings("unchecked")
public <T> T get(String key, Class<T> clazz) {
Object value = redisTemplate.opsForValue().get(key);
if (value == null) {
return null;
}
return (T) value;
}
/**
* 删除缓存
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 批量删除缓存
*/
public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}
/**
* 判断key是否存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取过期时间
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
/**
* 自增
*/
public Long increment(String key) {
return redisTemplate.opsForValue().increment(key);
}
/**
* 自增指定值
*/
public Long increment(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 自减
*/
public Long decrement(String key) {
return redisTemplate.opsForValue().decrement(key);
}
// ==================== Hash操作 ====================
/**
* 设置Hash字段
*/
public void hSet(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
/**
* 设置Hash多个字段
*/
public void hSetAll(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
}
/**
* 获取Hash字段
*/
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* 获取Hash所有字段
*/
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 删除Hash字段
*/
public Long hDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* 判断Hash字段是否存在
*/
public Boolean hHasKey(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
// ==================== List操作 ====================
/**
* 左推入List
*/
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* 右推入List
*/
public Long rPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* 左弹出List
*/
public Object lPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* 右弹出List
*/
public Object rPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
/**
* 获取List范围
*/
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 获取List长度
*/
public Long lSize(String key) {
return redisTemplate.opsForList().size(key);
}
// ==================== Set操作 ====================
/**
* 添加Set成员
*/
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* 移除Set成员
*/
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
/**
* 获取Set所有成员
*/
public Set<Object> sMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 判断是否为Set成员
*/
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 获取Set大小
*/
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
// ==================== ZSet操作 ====================
/**
* 添加ZSet成员
*/
public Boolean zAdd(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
* 移除ZSet成员
*/
public Long zRemove(String key, Object... values) {
return redisTemplate.opsForZSet().remove(key, values);
}
/**
* 获取ZSet范围
*/
public Set<Object> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
/**
* 获取ZSet大小
*/
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
}

View File

@ -0,0 +1,48 @@
package com.fundplatform.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
/**
* Redis配置类
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

View File

@ -40,6 +40,11 @@
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>4.0.0</version>
</dependency>
<!-- Nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -2,9 +2,11 @@ package com.fundplatform.cust;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(scanBasePackages = {"com.fundplatform.cust", "com.fundplatform.common"})
@EnableDiscoveryClient
@EnableFeignClients
public class CustApplication {

View File

@ -5,6 +5,13 @@ spring:
application:
name: fund-cust
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/fund_cust?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

View File

@ -33,6 +33,11 @@
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -2,8 +2,10 @@ package com.fundplatform.exp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ExpApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,65 @@
package com.fundplatform.exp.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.common.core.Result;
import com.fundplatform.exp.dto.FundExpenseDTO;
import com.fundplatform.exp.service.FundExpenseService;
import com.fundplatform.exp.vo.FundExpenseVO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/exp/expense")
public class FundExpenseController {
private final FundExpenseService expenseService;
public FundExpenseController(FundExpenseService expenseService) {
this.expenseService = expenseService;
}
@PostMapping
public Result<Long> create(@Valid @RequestBody FundExpenseDTO dto) {
return Result.success(expenseService.createExpense(dto));
}
@PutMapping
public Result<Boolean> update(@Valid @RequestBody FundExpenseDTO dto) {
return Result.success(expenseService.updateExpense(dto));
}
@GetMapping("/{id}")
public Result<FundExpenseVO> getById(@PathVariable Long id) {
return Result.success(expenseService.getExpenseById(id));
}
@GetMapping("/page")
public Result<Page<FundExpenseVO>> page(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String title,
@RequestParam(required = false) Integer expenseType,
@RequestParam(required = false) Integer payStatus) {
return Result.success(expenseService.pageExpenses(pageNum, pageSize, title, expenseType, payStatus));
}
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(expenseService.deleteExpense(id));
}
@PutMapping("/{id}/approve")
public Result<Boolean> approve(@PathVariable Long id, @RequestParam(required = false) String comment) {
return Result.success(expenseService.approve(id, comment));
}
@PutMapping("/{id}/reject")
public Result<Boolean> reject(@PathVariable Long id, @RequestParam(required = false) String comment) {
return Result.success(expenseService.reject(id, comment));
}
@PutMapping("/{id}/confirm-pay")
public Result<Boolean> confirmPay(@PathVariable Long id, @RequestParam String payChannel, @RequestParam(required = false) String payVoucher) {
return Result.success(expenseService.confirmPay(id, payChannel, payVoucher));
}
}

View File

@ -0,0 +1,256 @@
package com.fundplatform.exp.data.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fundplatform.common.core.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 支出管理实体
*/
@TableName("fund_expense")
public class FundExpense extends BaseEntity {
/** 支出单号 */
private String expenseNo;
/** 支出标题 */
private String title;
/** 支出金额 */
private BigDecimal amount;
/** 币种 */
private String currency;
/** 支出类型(1-日常支出 2-项目支出 3-工资发放 4-其他) */
private Integer expenseType;
/** 收款单位 */
private String payeeName;
/** 收款银行 */
private String payeeBank;
/** 收款账号 */
private String payeeAccount;
/** 支出日期 */
private LocalDateTime expenseDate;
/** 用途说明 */
private String purpose;
/** 关联用款申请ID */
private Long requestId;
/** 项目ID */
private Long projectId;
/** 客户ID */
private Long customerId;
/** 支付状态(0-待支付 1-已支付 2-支付失败) */
private Integer payStatus;
/** 支付时间 */
private LocalDateTime payTime;
/** 支付渠道 */
private String payChannel;
/** 支付凭证 */
private String payVoucher;
/** 审批状态 */
private Integer approvalStatus;
/** 审批人ID */
private Long approverId;
/** 审批时间 */
private LocalDateTime approvalTime;
/** 审批意见 */
private String approvalComment;
/** 附件URL */
private String attachments;
public String getExpenseNo() {
return expenseNo;
}
public void setExpenseNo(String expenseNo) {
this.expenseNo = expenseNo;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public Integer getExpenseType() {
return expenseType;
}
public void setExpenseType(Integer expenseType) {
this.expenseType = expenseType;
}
public String getPayeeName() {
return payeeName;
}
public void setPayeeName(String payeeName) {
this.payeeName = payeeName;
}
public String getPayeeBank() {
return payeeBank;
}
public void setPayeeBank(String payeeBank) {
this.payeeBank = payeeBank;
}
public String getPayeeAccount() {
return payeeAccount;
}
public void setPayeeAccount(String payeeAccount) {
this.payeeAccount = payeeAccount;
}
public LocalDateTime getExpenseDate() {
return expenseDate;
}
public void setExpenseDate(LocalDateTime expenseDate) {
this.expenseDate = expenseDate;
}
public String getPurpose() {
return purpose;
}
public void setPurpose(String purpose) {
this.purpose = purpose;
}
public Long getRequestId() {
return requestId;
}
public void setRequestId(Long requestId) {
this.requestId = requestId;
}
public Long getProjectId() {
return projectId;
}
public void setProjectId(Long projectId) {
this.projectId = projectId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public Integer getPayStatus() {
return payStatus;
}
public void setPayStatus(Integer payStatus) {
this.payStatus = payStatus;
}
public LocalDateTime getPayTime() {
return payTime;
}
public void setPayTime(LocalDateTime payTime) {
this.payTime = payTime;
}
public String getPayChannel() {
return payChannel;
}
public void setPayChannel(String payChannel) {
this.payChannel = payChannel;
}
public String getPayVoucher() {
return payVoucher;
}
public void setPayVoucher(String payVoucher) {
this.payVoucher = payVoucher;
}
public Integer getApprovalStatus() {
return approvalStatus;
}
public void setApprovalStatus(Integer approvalStatus) {
this.approvalStatus = approvalStatus;
}
public Long getApproverId() {
return approverId;
}
public void setApproverId(Long approverId) {
this.approverId = approverId;
}
public LocalDateTime getApprovalTime() {
return approvalTime;
}
public void setApprovalTime(LocalDateTime approvalTime) {
this.approvalTime = approvalTime;
}
public String getApprovalComment() {
return approvalComment;
}
public void setApprovalComment(String approvalComment) {
this.approvalComment = approvalComment;
}
public String getAttachments() {
return attachments;
}
public void setAttachments(String attachments) {
this.attachments = attachments;
}
}

View File

@ -0,0 +1,9 @@
package com.fundplatform.exp.data.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fundplatform.exp.data.entity.FundExpense;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FundExpenseMapper extends BaseMapper<FundExpense> {
}

View File

@ -0,0 +1,11 @@
package com.fundplatform.exp.data.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fundplatform.exp.data.entity.FundExpense;
import com.fundplatform.exp.data.mapper.FundExpenseMapper;
import org.springframework.stereotype.Service;
@Service
public class FundExpenseDataService extends ServiceImpl<FundExpenseMapper, FundExpense> implements IService<FundExpense> {
}

View File

@ -0,0 +1,70 @@
package com.fundplatform.exp.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class FundExpenseDTO {
private Long id;
@NotBlank(message = "支出标题不能为空")
private String title;
@NotNull(message = "支出金额不能为空")
@Positive(message = "支出金额必须大于0")
private BigDecimal amount;
private String currency = "CNY";
@NotNull(message = "支出类型不能为空")
private Integer expenseType;
@NotBlank(message = "收款单位不能为空")
private String payeeName;
private String payeeBank;
private String payeeAccount;
private LocalDateTime expenseDate;
private String purpose;
private Long requestId;
private Long projectId;
private Long customerId;
private String attachments;
private String remark;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public Integer getExpenseType() { return expenseType; }
public void setExpenseType(Integer expenseType) { this.expenseType = expenseType; }
public String getPayeeName() { return payeeName; }
public void setPayeeName(String payeeName) { this.payeeName = payeeName; }
public String getPayeeBank() { return payeeBank; }
public void setPayeeBank(String payeeBank) { this.payeeBank = payeeBank; }
public String getPayeeAccount() { return payeeAccount; }
public void setPayeeAccount(String payeeAccount) { this.payeeAccount = payeeAccount; }
public LocalDateTime getExpenseDate() { return expenseDate; }
public void setExpenseDate(LocalDateTime expenseDate) { this.expenseDate = expenseDate; }
public String getPurpose() { return purpose; }
public void setPurpose(String purpose) { this.purpose = purpose; }
public Long getRequestId() { return requestId; }
public void setRequestId(Long requestId) { this.requestId = requestId; }
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public Long getCustomerId() { return customerId; }
public void setCustomerId(Long customerId) { this.customerId = customerId; }
public String getAttachments() { return attachments; }
public void setAttachments(String attachments) { this.attachments = attachments; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
}

View File

@ -0,0 +1,24 @@
package com.fundplatform.exp.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.exp.dto.FundExpenseDTO;
import com.fundplatform.exp.vo.FundExpenseVO;
public interface FundExpenseService {
Long createExpense(FundExpenseDTO dto);
boolean updateExpense(FundExpenseDTO dto);
FundExpenseVO getExpenseById(Long id);
Page<FundExpenseVO> pageExpenses(int pageNum, int pageSize, String title, Integer expenseType, Integer payStatus);
boolean deleteExpense(Long id);
boolean approve(Long id, String comment);
boolean reject(Long id, String comment);
boolean confirmPay(Long id, String payChannel, String payVoucher);
}

View File

@ -0,0 +1,198 @@
package com.fundplatform.exp.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.exp.data.entity.FundExpense;
import com.fundplatform.exp.data.service.FundExpenseDataService;
import com.fundplatform.exp.dto.FundExpenseDTO;
import com.fundplatform.exp.service.FundExpenseService;
import com.fundplatform.exp.vo.FundExpenseVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
@Service
public class FundExpenseServiceImpl implements FundExpenseService {
private static final Logger log = LoggerFactory.getLogger(FundExpenseServiceImpl.class);
private static final AtomicInteger counter = new AtomicInteger(1);
private final FundExpenseDataService expenseDataService;
public FundExpenseServiceImpl(FundExpenseDataService expenseDataService) {
this.expenseDataService = expenseDataService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createExpense(FundExpenseDTO dto) {
FundExpense expense = new FundExpense();
expense.setExpenseNo(generateExpenseNo());
expense.setTitle(dto.getTitle());
expense.setAmount(dto.getAmount());
expense.setCurrency(dto.getCurrency() != null ? dto.getCurrency() : "CNY");
expense.setExpenseType(dto.getExpenseType());
expense.setPayeeName(dto.getPayeeName());
expense.setPayeeBank(dto.getPayeeBank());
expense.setPayeeAccount(dto.getPayeeAccount());
expense.setExpenseDate(dto.getExpenseDate() != null ? dto.getExpenseDate() : LocalDateTime.now());
expense.setPurpose(dto.getPurpose());
expense.setRequestId(dto.getRequestId());
expense.setProjectId(dto.getProjectId());
expense.setCustomerId(dto.getCustomerId());
expense.setPayStatus(0);
expense.setApprovalStatus(0);
expense.setAttachments(dto.getAttachments());
expense.setDeleted(0);
expense.setCreatedTime(LocalDateTime.now());
expenseDataService.save(expense);
log.info("创建支出记录成功: id={}, expenseNo={}", expense.getId(), expense.getExpenseNo());
return expense.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateExpense(FundExpenseDTO dto) {
if (dto.getId() == null) throw new RuntimeException("支出ID不能为空");
FundExpense existing = expenseDataService.getById(dto.getId());
if (existing == null || existing.getDeleted() == 1) throw new RuntimeException("支出记录不存在");
if (existing.getPayStatus() != 0) throw new RuntimeException("当前状态不允许修改");
FundExpense expense = new FundExpense();
expense.setId(dto.getId());
expense.setTitle(dto.getTitle());
expense.setAmount(dto.getAmount());
expense.setPurpose(dto.getPurpose());
expense.setAttachments(dto.getAttachments());
expense.setUpdatedTime(LocalDateTime.now());
return expenseDataService.updateById(expense);
}
@Override
public FundExpenseVO getExpenseById(Long id) {
FundExpense expense = expenseDataService.getById(id);
if (expense == null || expense.getDeleted() == 1) throw new RuntimeException("支出记录不存在");
return convertToVO(expense);
}
@Override
public Page<FundExpenseVO> pageExpenses(int pageNum, int pageSize, String title, Integer expenseType, Integer payStatus) {
Page<FundExpense> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<FundExpense> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FundExpense::getDeleted, 0);
if (StringUtils.hasText(title)) wrapper.like(FundExpense::getTitle, title);
if (expenseType != null) wrapper.eq(FundExpense::getExpenseType, expenseType);
if (payStatus != null) wrapper.eq(FundExpense::getPayStatus, payStatus);
wrapper.orderByDesc(FundExpense::getCreatedTime);
Page<FundExpense> expensePage = expenseDataService.page(page, wrapper);
Page<FundExpenseVO> voPage = new Page<>(expensePage.getCurrent(), expensePage.getSize(), expensePage.getTotal());
voPage.setRecords(expensePage.getRecords().stream().map(this::convertToVO).toList());
return voPage;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteExpense(Long id) {
LambdaUpdateWrapper<FundExpense> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundExpense::getId, id).set(FundExpense::getDeleted, 1).set(FundExpense::getUpdatedTime, LocalDateTime.now());
return expenseDataService.update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean approve(Long id, String comment) {
LambdaUpdateWrapper<FundExpense> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundExpense::getId, id).set(FundExpense::getApprovalStatus, 2)
.set(FundExpense::getApprovalTime, LocalDateTime.now())
.set(FundExpense::getApprovalComment, comment)
.set(FundExpense::getUpdatedTime, LocalDateTime.now());
return expenseDataService.update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean reject(Long id, String comment) {
LambdaUpdateWrapper<FundExpense> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundExpense::getId, id).set(FundExpense::getApprovalStatus, 3)
.set(FundExpense::getApprovalTime, LocalDateTime.now())
.set(FundExpense::getApprovalComment, comment)
.set(FundExpense::getUpdatedTime, LocalDateTime.now());
return expenseDataService.update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean confirmPay(Long id, String payChannel, String payVoucher) {
LambdaUpdateWrapper<FundExpense> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundExpense::getId, id).set(FundExpense::getPayStatus, 1)
.set(FundExpense::getPayTime, LocalDateTime.now())
.set(FundExpense::getPayChannel, payChannel)
.set(FundExpense::getPayVoucher, payVoucher)
.set(FundExpense::getUpdatedTime, LocalDateTime.now());
return expenseDataService.update(wrapper);
}
private String generateExpenseNo() {
String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
int seq = counter.getAndIncrement();
return String.format("EXP%s%04d", dateStr, seq);
}
private FundExpenseVO convertToVO(FundExpense e) {
FundExpenseVO vo = new FundExpenseVO();
vo.setId(e.getId());
vo.setExpenseNo(e.getExpenseNo());
vo.setTitle(e.getTitle());
vo.setAmount(e.getAmount());
vo.setCurrency(e.getCurrency());
vo.setExpenseType(e.getExpenseType());
vo.setExpenseTypeName(getExpenseTypeName(e.getExpenseType()));
vo.setPayeeName(e.getPayeeName());
vo.setPayeeBank(e.getPayeeBank());
vo.setPayeeAccount(e.getPayeeAccount());
vo.setExpenseDate(e.getExpenseDate());
vo.setPurpose(e.getPurpose());
vo.setRequestId(e.getRequestId());
vo.setProjectId(e.getProjectId());
vo.setCustomerId(e.getCustomerId());
vo.setPayStatus(e.getPayStatus());
vo.setPayStatusName(getPayStatusName(e.getPayStatus()));
vo.setPayTime(e.getPayTime());
vo.setPayChannel(e.getPayChannel());
vo.setPayVoucher(e.getPayVoucher());
vo.setApprovalStatus(e.getApprovalStatus());
vo.setApprovalStatusName(getApprovalStatusName(e.getApprovalStatus()));
vo.setApprovalTime(e.getApprovalTime());
vo.setApprovalComment(e.getApprovalComment());
vo.setAttachments(e.getAttachments());
vo.setTenantId(e.getTenantId());
vo.setCreatedBy(e.getCreatedBy());
vo.setCreatedTime(e.getCreatedTime());
return vo;
}
private String getExpenseTypeName(Integer type) {
if (type == null) return "";
return switch (type) { case 1 -> "日常支出"; case 2 -> "项目支出"; case 3 -> "工资发放"; case 4 -> "其他"; default -> ""; };
}
private String getPayStatusName(Integer status) {
if (status == null) return "";
return switch (status) { case 0 -> "待支付"; case 1 -> "已支付"; case 2 -> "支付失败"; default -> ""; };
}
private String getApprovalStatusName(Integer status) {
if (status == null) return "";
return switch (status) { case 0 -> "待审批"; case 1 -> "审批中"; case 2 -> "审批通过"; case 3 -> "审批拒绝"; default -> ""; };
}
}

View File

@ -0,0 +1,97 @@
package com.fundplatform.exp.vo;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class FundExpenseVO {
private Long id;
private String expenseNo;
private String title;
private BigDecimal amount;
private String currency;
private Integer expenseType;
private String expenseTypeName;
private String payeeName;
private String payeeBank;
private String payeeAccount;
private LocalDateTime expenseDate;
private String purpose;
private Long requestId;
private Long projectId;
private Long customerId;
private Integer payStatus;
private String payStatusName;
private LocalDateTime payTime;
private String payChannel;
private String payVoucher;
private Integer approvalStatus;
private String approvalStatusName;
private Long approverId;
private LocalDateTime approvalTime;
private String approvalComment;
private String attachments;
private Long tenantId;
private Long createdBy;
private LocalDateTime createdTime;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getExpenseNo() { return expenseNo; }
public void setExpenseNo(String expenseNo) { this.expenseNo = expenseNo; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public Integer getExpenseType() { return expenseType; }
public void setExpenseType(Integer expenseType) { this.expenseType = expenseType; }
public String getExpenseTypeName() { return expenseTypeName; }
public void setExpenseTypeName(String expenseTypeName) { this.expenseTypeName = expenseTypeName; }
public String getPayeeName() { return payeeName; }
public void setPayeeName(String payeeName) { this.payeeName = payeeName; }
public String getPayeeBank() { return payeeBank; }
public void setPayeeBank(String payeeBank) { this.payeeBank = payeeBank; }
public String getPayeeAccount() { return payeeAccount; }
public void setPayeeAccount(String payeeAccount) { this.payeeAccount = payeeAccount; }
public LocalDateTime getExpenseDate() { return expenseDate; }
public void setExpenseDate(LocalDateTime expenseDate) { this.expenseDate = expenseDate; }
public String getPurpose() { return purpose; }
public void setPurpose(String purpose) { this.purpose = purpose; }
public Long getRequestId() { return requestId; }
public void setRequestId(Long requestId) { this.requestId = requestId; }
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public Long getCustomerId() { return customerId; }
public void setCustomerId(Long customerId) { this.customerId = customerId; }
public Integer getPayStatus() { return payStatus; }
public void setPayStatus(Integer payStatus) { this.payStatus = payStatus; }
public String getPayStatusName() { return payStatusName; }
public void setPayStatusName(String payStatusName) { this.payStatusName = payStatusName; }
public LocalDateTime getPayTime() { return payTime; }
public void setPayTime(LocalDateTime payTime) { this.payTime = payTime; }
public String getPayChannel() { return payChannel; }
public void setPayChannel(String payChannel) { this.payChannel = payChannel; }
public String getPayVoucher() { return payVoucher; }
public void setPayVoucher(String payVoucher) { this.payVoucher = payVoucher; }
public Integer getApprovalStatus() { return approvalStatus; }
public void setApprovalStatus(Integer approvalStatus) { this.approvalStatus = approvalStatus; }
public String getApprovalStatusName() { return approvalStatusName; }
public void setApprovalStatusName(String approvalStatusName) { this.approvalStatusName = approvalStatusName; }
public Long getApproverId() { return approverId; }
public void setApproverId(Long approverId) { this.approverId = approverId; }
public LocalDateTime getApprovalTime() { return approvalTime; }
public void setApprovalTime(LocalDateTime approvalTime) { this.approvalTime = approvalTime; }
public String getApprovalComment() { return approvalComment; }
public void setApprovalComment(String approvalComment) { this.approvalComment = approvalComment; }
public String getAttachments() { return attachments; }
public void setAttachments(String attachments) { this.attachments = attachments; }
public Long getTenantId() { return tenantId; }
public void setTenantId(Long tenantId) { this.tenantId = tenantId; }
public Long getCreatedBy() { return createdBy; }
public void setCreatedBy(Long createdBy) { this.createdBy = createdBy; }
public LocalDateTime getCreatedTime() { return createdTime; }
public void setCreatedTime(LocalDateTime createdTime) { this.createdTime = createdTime; }
}

View File

@ -4,3 +4,10 @@ server:
spring:
application:
name: fund-exp
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP

View File

@ -33,6 +33,11 @@
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -2,8 +2,10 @@ package com.fundplatform.file;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class FileApplication {
public static void main(String[] args) {

View File

@ -4,3 +4,10 @@ server:
spring:
application:
name: fund-file
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP

View File

@ -34,6 +34,31 @@
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>4.0.0</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Redis for Rate Limiting -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -0,0 +1,45 @@
package com.fundplatform.gateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
/**
* 限流配置
*/
@Configuration
public class RateLimitConfig {
/**
* 基于IP的限流Key解析器
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> {
String ip = exchange.getRequest().getRemoteAddress() != null
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
: "unknown";
return Mono.just(ip);
};
}
/**
* 基于用户ID的限流Key解析器
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
return Mono.just(userId != null ? userId : "anonymous");
};
}
/**
* 基于API路径的限流Key解析器
*/
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}

View File

@ -0,0 +1,88 @@
package com.fundplatform.gateway.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.UUID;
/**
* 全局日志过滤器
* 记录所有请求日志,生成TraceId
*/
@Component
public class GlobalLogFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(GlobalLogFilter.class);
private static final String TRACE_ID = "X-Trace-Id";
private static final String START_TIME = "startTime";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 生成TraceId
String traceId = UUID.randomUUID().toString().replace("-", "");
// 记录开始时间
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
// 将TraceId写入请求头
ServerHttpRequest request = exchange.getRequest().mutate()
.header(TRACE_ID, traceId)
.build();
// 记录请求日志
String method = request.getMethod().name();
String path = request.getURI().getPath();
String clientIp = getClientIp(request);
logger.info("[{}] Request: {} {} from {}", traceId, method, path, clientIp);
// 继续执行
return chain.filter(exchange.mutate().request(request).build())
.then(Mono.fromRunnable(() -> {
// 计算耗时
Long startTime = exchange.getAttribute(START_TIME);
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
int statusCode = exchange.getResponse().getStatusCode() != null
? exchange.getResponse().getStatusCode().value()
: 0;
logger.info("[{}] Response: {} {} - {} ({}ms)",
traceId, method, path, statusCode, duration);
}
}));
}
/**
* 获取客户端IP
*/
private String getClientIp(ServerHttpRequest request) {
String ip = request.getHeaders().getFirst("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeaders().getFirst("X-Real-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddress() != null
? request.getRemoteAddress().getAddress().getHostAddress()
: "unknown";
}
// 多个代理时取第一个IP
if (ip != null && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}

View File

@ -0,0 +1,158 @@
package com.fundplatform.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fundplatform.common.core.Result;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
/**
* JWT鉴权过滤器
* 验证JWT Token,提取用户信息
*/
@Component
public class JwtAuthFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class);
private static final String SECRET_KEY = "fundplatform-secret-key-for-jwt-token-generation-min-256-bits";
private static final String TOKEN_PREFIX = "Bearer ";
private static final String USER_ID_HEADER = "X-User-Id";
private static final String USERNAME_HEADER = "X-Username";
private static final String TENANT_ID_HEADER = "X-Tenant-Id";
// 白名单路径(不需要token验证)
private static final List<String> WHITE_LIST = Arrays.asList(
"/sys/api/v1/auth/login",
"/sys/api/v1/sys/health",
"/cust/api/v1/cust/health",
"/proj/api/v1/proj/health"
);
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 白名单路径直接放行
if (isWhiteListed(path)) {
return chain.filter(exchange);
}
// 获取Token
String token = getToken(request);
if (token == null) {
return unauthorized(exchange, "缺少认证Token");
}
try {
// 验证Token
Claims claims = validateToken(token);
if (claims == null) {
return unauthorized(exchange, "Token无效或已过期");
}
// 提取用户信息
Long userId = claims.get("userId", Long.class);
String username = claims.get("username", String.class);
Long tenantId = claims.get("tenantId", Long.class);
// 将用户信息写入请求头
ServerHttpRequest mutatedRequest = request.mutate()
.header(USER_ID_HEADER, String.valueOf(userId))
.header(USERNAME_HEADER, username)
.header(TENANT_ID_HEADER, String.valueOf(tenantId))
.build();
logger.debug("Token验证通过: userId={}, username={}, tenantId={}", userId, username, tenantId);
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
logger.error("Token验证失败: {}", e.getMessage());
return unauthorized(exchange, "Token验证失败: " + e.getMessage());
}
}
/**
* 判断是否白名单路径
*/
private boolean isWhiteListed(String path) {
return WHITE_LIST.stream().anyMatch(path::startsWith);
}
/**
* 从请求头获取Token
*/
private String getToken(ServerHttpRequest request) {
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith(TOKEN_PREFIX)) {
return authHeader.substring(TOKEN_PREFIX.length());
}
return null;
}
/**
* 验证Token
*/
private Claims validateToken(String token) {
try {
SecretKey key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8));
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
logger.error("Token解析失败: {}", e.getMessage());
return null;
}
}
/**
* 返回未授权响应
*/
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Result<Void> result = Result.error(401, message);
String body;
try {
body = objectMapper.writeValueAsString(result);
} catch (JsonProcessingException e) {
body = "{\"code\":401,\"message\":\"Unauthorized\",\"success\":false}";
}
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

View File

@ -4,10 +4,27 @@ server:
spring:
application:
name: fund-gateway
# Redis配置(用于限流)
data:
redis:
host: localhost
port: 6379
password: zjf@123456
database: 1
cloud:
compatibility-verifier:
enabled: false
gateway:
# 默认限流配置
default-filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100 # 每秒补充令牌数
redis-rate-limiter.burstCapacity: 200 # 令牌桶最大容量
key-resolver: "#{@ipKeyResolver}"
routes:
# 系统管理服务
- id: fund-sys

View File

@ -33,6 +33,11 @@
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -2,8 +2,10 @@ package com.fundplatform.proj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication(scanBasePackages = {"com.fundplatform.proj", "com.fundplatform.common"})
@EnableDiscoveryClient
public class ProjApplication {
public static void main(String[] args) {

View File

@ -5,6 +5,13 @@ spring:
application:
name: fund-proj
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/fund_proj?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

View File

@ -2,8 +2,10 @@ package com.fundplatform.receipt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ReceiptApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,60 @@
package com.fundplatform.receipt.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.common.core.Result;
import com.fundplatform.receipt.dto.FundReceiptDTO;
import com.fundplatform.receipt.service.FundReceiptService;
import com.fundplatform.receipt.vo.FundReceiptVO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/receipt/receipt")
public class FundReceiptController {
private final FundReceiptService receiptService;
public FundReceiptController(FundReceiptService receiptService) {
this.receiptService = receiptService;
}
@PostMapping
public Result<Long> create(@Valid @RequestBody FundReceiptDTO dto) {
return Result.success(receiptService.createReceipt(dto));
}
@PutMapping
public Result<Boolean> update(@Valid @RequestBody FundReceiptDTO dto) {
return Result.success(receiptService.updateReceipt(dto));
}
@GetMapping("/{id}")
public Result<FundReceiptVO> getById(@PathVariable Long id) {
return Result.success(receiptService.getReceiptById(id));
}
@GetMapping("/page")
public Result<Page<FundReceiptVO>> page(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String title,
@RequestParam(required = false) Integer receiptType,
@RequestParam(required = false) Integer receiptStatus) {
return Result.success(receiptService.pageReceipts(pageNum, pageSize, title, receiptType, receiptStatus));
}
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
return Result.success(receiptService.deleteReceipt(id));
}
@PutMapping("/{id}/confirm")
public Result<Boolean> confirm(@PathVariable Long id) {
return Result.success(receiptService.confirm(id));
}
@PutMapping("/{id}/write-off")
public Result<Boolean> writeOff(@PathVariable Long id) {
return Result.success(receiptService.writeOff(id));
}
}

View File

@ -0,0 +1,115 @@
package com.fundplatform.receipt.data.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fundplatform.common.core.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 收款管理实体
*/
@TableName("fund_receipt")
public class FundReceipt extends BaseEntity {
/** 收款单号 */
private String receiptNo;
/** 收款标题 */
private String title;
/** 收款金额 */
private BigDecimal amount;
/** 币种 */
private String currency;
/** 收款类型(1-项目收款 2-服务费 3-利息收入 4-其他) */
private Integer receiptType;
/** 付款单位 */
private String payerName;
/** 付款银行 */
private String payerBank;
/** 付款账号 */
private String payerAccount;
/** 收款日期 */
private LocalDateTime receiptDate;
/** 用途说明 */
private String purpose;
/** 关联项目ID */
private Long projectId;
/** 关联客户ID */
private Long customerId;
/** 收款状态(0-待确认 1-已确认 2-已核销) */
private Integer receiptStatus;
/** 确认时间 */
private LocalDateTime confirmTime;
/** 确认人ID */
private Long confirmBy;
/** 核销时间 */
private LocalDateTime writeOffTime;
/** 核销人ID */
private Long writeOffBy;
/** 收款凭证 */
private String voucher;
/** 发票号 */
private String invoiceNo;
/** 附件URL */
private String attachments;
public String getReceiptNo() { return receiptNo; }
public void setReceiptNo(String receiptNo) { this.receiptNo = receiptNo; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public Integer getReceiptType() { return receiptType; }
public void setReceiptType(Integer receiptType) { this.receiptType = receiptType; }
public String getPayerName() { return payerName; }
public void setPayerName(String payerName) { this.payerName = payerName; }
public String getPayerBank() { return payerBank; }
public void setPayerBank(String payerBank) { this.payerBank = payerBank; }
public String getPayerAccount() { return payerAccount; }
public void setPayerAccount(String payerAccount) { this.payerAccount = payerAccount; }
public LocalDateTime getReceiptDate() { return receiptDate; }
public void setReceiptDate(LocalDateTime receiptDate) { this.receiptDate = receiptDate; }
public String getPurpose() { return purpose; }
public void setPurpose(String purpose) { this.purpose = purpose; }
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public Long getCustomerId() { return customerId; }
public void setCustomerId(Long customerId) { this.customerId = customerId; }
public Integer getReceiptStatus() { return receiptStatus; }
public void setReceiptStatus(Integer receiptStatus) { this.receiptStatus = receiptStatus; }
public LocalDateTime getConfirmTime() { return confirmTime; }
public void setConfirmTime(LocalDateTime confirmTime) { this.confirmTime = confirmTime; }
public Long getConfirmBy() { return confirmBy; }
public void setConfirmBy(Long confirmBy) { this.confirmBy = confirmBy; }
public LocalDateTime getWriteOffTime() { return writeOffTime; }
public void setWriteOffTime(LocalDateTime writeOffTime) { this.writeOffTime = writeOffTime; }
public Long getWriteOffBy() { return writeOffBy; }
public void setWriteOffBy(Long writeOffBy) { this.writeOffBy = writeOffBy; }
public String getVoucher() { return voucher; }
public void setVoucher(String voucher) { this.voucher = voucher; }
public String getInvoiceNo() { return invoiceNo; }
public void setInvoiceNo(String invoiceNo) { this.invoiceNo = invoiceNo; }
public String getAttachments() { return attachments; }
public void setAttachments(String attachments) { this.attachments = attachments; }
}

View File

@ -0,0 +1,9 @@
package com.fundplatform.receipt.data.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fundplatform.receipt.data.entity.FundReceipt;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FundReceiptMapper extends BaseMapper<FundReceipt> {
}

View File

@ -0,0 +1,11 @@
package com.fundplatform.receipt.data.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fundplatform.receipt.data.entity.FundReceipt;
import com.fundplatform.receipt.data.mapper.FundReceiptMapper;
import org.springframework.stereotype.Service;
@Service
public class FundReceiptDataService extends ServiceImpl<FundReceiptMapper, FundReceipt> implements IService<FundReceipt> {
}

View File

@ -0,0 +1,71 @@
package com.fundplatform.receipt.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class FundReceiptDTO {
private Long id;
@NotBlank(message = "收款标题不能为空")
private String title;
@NotNull(message = "收款金额不能为空")
@Positive(message = "收款金额必须大于0")
private BigDecimal amount;
private String currency = "CNY";
@NotNull(message = "收款类型不能为空")
private Integer receiptType;
@NotBlank(message = "付款单位不能为空")
private String payerName;
private String payerBank;
private String payerAccount;
private LocalDateTime receiptDate;
private String purpose;
private Long projectId;
private Long customerId;
private String invoiceNo;
private String voucher;
private String attachments;
private String remark;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public Integer getReceiptType() { return receiptType; }
public void setReceiptType(Integer receiptType) { this.receiptType = receiptType; }
public String getPayerName() { return payerName; }
public void setPayerName(String payerName) { this.payerName = payerName; }
public String getPayerBank() { return payerBank; }
public void setPayerBank(String payerBank) { this.payerBank = payerBank; }
public String getPayerAccount() { return payerAccount; }
public void setPayerAccount(String payerAccount) { this.payerAccount = payerAccount; }
public LocalDateTime getReceiptDate() { return receiptDate; }
public void setReceiptDate(LocalDateTime receiptDate) { this.receiptDate = receiptDate; }
public String getPurpose() { return purpose; }
public void setPurpose(String purpose) { this.purpose = purpose; }
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public Long getCustomerId() { return customerId; }
public void setCustomerId(Long customerId) { this.customerId = customerId; }
public String getInvoiceNo() { return invoiceNo; }
public void setInvoiceNo(String invoiceNo) { this.invoiceNo = invoiceNo; }
public String getVoucher() { return voucher; }
public void setVoucher(String voucher) { this.voucher = voucher; }
public String getAttachments() { return attachments; }
public void setAttachments(String attachments) { this.attachments = attachments; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
}

View File

@ -0,0 +1,22 @@
package com.fundplatform.receipt.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.receipt.dto.FundReceiptDTO;
import com.fundplatform.receipt.vo.FundReceiptVO;
public interface FundReceiptService {
Long createReceipt(FundReceiptDTO dto);
boolean updateReceipt(FundReceiptDTO dto);
FundReceiptVO getReceiptById(Long id);
Page<FundReceiptVO> pageReceipts(int pageNum, int pageSize, String title, Integer receiptType, Integer receiptStatus);
boolean deleteReceipt(Long id);
boolean confirm(Long id);
boolean writeOff(Long id);
}

View File

@ -0,0 +1,178 @@
package com.fundplatform.receipt.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.receipt.data.entity.FundReceipt;
import com.fundplatform.receipt.data.service.FundReceiptDataService;
import com.fundplatform.receipt.dto.FundReceiptDTO;
import com.fundplatform.receipt.service.FundReceiptService;
import com.fundplatform.receipt.vo.FundReceiptVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
@Service
public class FundReceiptServiceImpl implements FundReceiptService {
private static final Logger log = LoggerFactory.getLogger(FundReceiptServiceImpl.class);
private static final AtomicInteger counter = new AtomicInteger(1);
private final FundReceiptDataService receiptDataService;
public FundReceiptServiceImpl(FundReceiptDataService receiptDataService) {
this.receiptDataService = receiptDataService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createReceipt(FundReceiptDTO dto) {
FundReceipt receipt = new FundReceipt();
receipt.setReceiptNo(generateReceiptNo());
receipt.setTitle(dto.getTitle());
receipt.setAmount(dto.getAmount());
receipt.setCurrency(dto.getCurrency() != null ? dto.getCurrency() : "CNY");
receipt.setReceiptType(dto.getReceiptType());
receipt.setPayerName(dto.getPayerName());
receipt.setPayerBank(dto.getPayerBank());
receipt.setPayerAccount(dto.getPayerAccount());
receipt.setReceiptDate(dto.getReceiptDate() != null ? dto.getReceiptDate() : LocalDateTime.now());
receipt.setPurpose(dto.getPurpose());
receipt.setProjectId(dto.getProjectId());
receipt.setCustomerId(dto.getCustomerId());
receipt.setReceiptStatus(0);
receipt.setInvoiceNo(dto.getInvoiceNo());
receipt.setVoucher(dto.getVoucher());
receipt.setAttachments(dto.getAttachments());
receipt.setDeleted(0);
receipt.setCreatedTime(LocalDateTime.now());
receiptDataService.save(receipt);
log.info("创建收款记录成功: id={}, receiptNo={}", receipt.getId(), receipt.getReceiptNo());
return receipt.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateReceipt(FundReceiptDTO dto) {
if (dto.getId() == null) throw new RuntimeException("收款ID不能为空");
FundReceipt existing = receiptDataService.getById(dto.getId());
if (existing == null || existing.getDeleted() == 1) throw new RuntimeException("收款记录不存在");
if (existing.getReceiptStatus() == 2) throw new RuntimeException("已核销记录不允许修改");
FundReceipt receipt = new FundReceipt();
receipt.setId(dto.getId());
receipt.setTitle(dto.getTitle());
receipt.setAmount(dto.getAmount());
receipt.setPurpose(dto.getPurpose());
receipt.setVoucher(dto.getVoucher());
receipt.setAttachments(dto.getAttachments());
receipt.setUpdatedTime(LocalDateTime.now());
return receiptDataService.updateById(receipt);
}
@Override
public FundReceiptVO getReceiptById(Long id) {
FundReceipt receipt = receiptDataService.getById(id);
if (receipt == null || receipt.getDeleted() == 1) throw new RuntimeException("收款记录不存在");
return convertToVO(receipt);
}
@Override
public Page<FundReceiptVO> pageReceipts(int pageNum, int pageSize, String title, Integer receiptType, Integer receiptStatus) {
Page<FundReceipt> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<FundReceipt> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FundReceipt::getDeleted, 0);
if (StringUtils.hasText(title)) wrapper.like(FundReceipt::getTitle, title);
if (receiptType != null) wrapper.eq(FundReceipt::getReceiptType, receiptType);
if (receiptStatus != null) wrapper.eq(FundReceipt::getReceiptStatus, receiptStatus);
wrapper.orderByDesc(FundReceipt::getCreatedTime);
Page<FundReceipt> receiptPage = receiptDataService.page(page, wrapper);
Page<FundReceiptVO> voPage = new Page<>(receiptPage.getCurrent(), receiptPage.getSize(), receiptPage.getTotal());
voPage.setRecords(receiptPage.getRecords().stream().map(this::convertToVO).toList());
return voPage;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteReceipt(Long id) {
LambdaUpdateWrapper<FundReceipt> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundReceipt::getId, id).set(FundReceipt::getDeleted, 1).set(FundReceipt::getUpdatedTime, LocalDateTime.now());
return receiptDataService.update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean confirm(Long id) {
LambdaUpdateWrapper<FundReceipt> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundReceipt::getId, id).set(FundReceipt::getReceiptStatus, 1)
.set(FundReceipt::getConfirmTime, LocalDateTime.now())
.set(FundReceipt::getUpdatedTime, LocalDateTime.now());
return receiptDataService.update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean writeOff(Long id) {
LambdaUpdateWrapper<FundReceipt> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundReceipt::getId, id).set(FundReceipt::getReceiptStatus, 2)
.set(FundReceipt::getWriteOffTime, LocalDateTime.now())
.set(FundReceipt::getUpdatedTime, LocalDateTime.now());
return receiptDataService.update(wrapper);
}
private String generateReceiptNo() {
String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
int seq = counter.getAndIncrement();
return String.format("REC%s%04d", dateStr, seq);
}
private FundReceiptVO convertToVO(FundReceipt r) {
FundReceiptVO vo = new FundReceiptVO();
vo.setId(r.getId());
vo.setReceiptNo(r.getReceiptNo());
vo.setTitle(r.getTitle());
vo.setAmount(r.getAmount());
vo.setCurrency(r.getCurrency());
vo.setReceiptType(r.getReceiptType());
vo.setReceiptTypeName(getReceiptTypeName(r.getReceiptType()));
vo.setPayerName(r.getPayerName());
vo.setPayerBank(r.getPayerBank());
vo.setPayerAccount(r.getPayerAccount());
vo.setReceiptDate(r.getReceiptDate());
vo.setPurpose(r.getPurpose());
vo.setProjectId(r.getProjectId());
vo.setCustomerId(r.getCustomerId());
vo.setReceiptStatus(r.getReceiptStatus());
vo.setReceiptStatusName(getReceiptStatusName(r.getReceiptStatus()));
vo.setConfirmTime(r.getConfirmTime());
vo.setConfirmBy(r.getConfirmBy());
vo.setWriteOffTime(r.getWriteOffTime());
vo.setWriteOffBy(r.getWriteOffBy());
vo.setVoucher(r.getVoucher());
vo.setInvoiceNo(r.getInvoiceNo());
vo.setAttachments(r.getAttachments());
vo.setTenantId(r.getTenantId());
vo.setCreatedBy(r.getCreatedBy());
vo.setCreatedTime(r.getCreatedTime());
return vo;
}
private String getReceiptTypeName(Integer type) {
if (type == null) return "";
return switch (type) { case 1 -> "项目收款"; case 2 -> "服务费"; case 3 -> "利息收入"; case 4 -> "其他"; default -> ""; };
}
private String getReceiptStatusName(Integer status) {
if (status == null) return "";
return switch (status) { case 0 -> "待确认"; case 1 -> "已确认"; case 2 -> "已核销"; default -> ""; };
}
}

View File

@ -0,0 +1,87 @@
package com.fundplatform.receipt.vo;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public class FundReceiptVO {
private Long id;
private String receiptNo;
private String title;
private BigDecimal amount;
private String currency;
private Integer receiptType;
private String receiptTypeName;
private String payerName;
private String payerBank;
private String payerAccount;
private LocalDateTime receiptDate;
private String purpose;
private Long projectId;
private Long customerId;
private Integer receiptStatus;
private String receiptStatusName;
private LocalDateTime confirmTime;
private Long confirmBy;
private LocalDateTime writeOffTime;
private Long writeOffBy;
private String voucher;
private String invoiceNo;
private String attachments;
private Long tenantId;
private Long createdBy;
private LocalDateTime createdTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getReceiptNo() { return receiptNo; }
public void setReceiptNo(String receiptNo) { this.receiptNo = receiptNo; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public Integer getReceiptType() { return receiptType; }
public void setReceiptType(Integer receiptType) { this.receiptType = receiptType; }
public String getReceiptTypeName() { return receiptTypeName; }
public void setReceiptTypeName(String receiptTypeName) { this.receiptTypeName = receiptTypeName; }
public String getPayerName() { return payerName; }
public void setPayerName(String payerName) { this.payerName = payerName; }
public String getPayerBank() { return payerBank; }
public void setPayerBank(String payerBank) { this.payerBank = payerBank; }
public String getPayerAccount() { return payerAccount; }
public void setPayerAccount(String payerAccount) { this.payerAccount = payerAccount; }
public LocalDateTime getReceiptDate() { return receiptDate; }
public void setReceiptDate(LocalDateTime receiptDate) { this.receiptDate = receiptDate; }
public String getPurpose() { return purpose; }
public void setPurpose(String purpose) { this.purpose = purpose; }
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public Long getCustomerId() { return customerId; }
public void setCustomerId(Long customerId) { this.customerId = customerId; }
public Integer getReceiptStatus() { return receiptStatus; }
public void setReceiptStatus(Integer receiptStatus) { this.receiptStatus = receiptStatus; }
public String getReceiptStatusName() { return receiptStatusName; }
public void setReceiptStatusName(String receiptStatusName) { this.receiptStatusName = receiptStatusName; }
public LocalDateTime getConfirmTime() { return confirmTime; }
public void setConfirmTime(LocalDateTime confirmTime) { this.confirmTime = confirmTime; }
public Long getConfirmBy() { return confirmBy; }
public void setConfirmBy(Long confirmBy) { this.confirmBy = confirmBy; }
public LocalDateTime getWriteOffTime() { return writeOffTime; }
public void setWriteOffTime(LocalDateTime writeOffTime) { this.writeOffTime = writeOffTime; }
public Long getWriteOffBy() { return writeOffBy; }
public void setWriteOffBy(Long writeOffBy) { this.writeOffBy = writeOffBy; }
public String getVoucher() { return voucher; }
public void setVoucher(String voucher) { this.voucher = voucher; }
public String getInvoiceNo() { return invoiceNo; }
public void setInvoiceNo(String invoiceNo) { this.invoiceNo = invoiceNo; }
public String getAttachments() { return attachments; }
public void setAttachments(String attachments) { this.attachments = attachments; }
public Long getTenantId() { return tenantId; }
public void setTenantId(Long tenantId) { this.tenantId = tenantId; }
public Long getCreatedBy() { return createdBy; }
public void setCreatedBy(Long createdBy) { this.createdBy = createdBy; }
public LocalDateTime getCreatedTime() { return createdTime; }
public void setCreatedTime(LocalDateTime createdTime) { this.createdTime = createdTime; }
}

View File

@ -4,3 +4,10 @@ server:
spring:
application:
name: fund-receipt
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP

View File

@ -2,8 +2,10 @@ package com.fundplatform.report;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ReportApplication {
public static void main(String[] args) {

View File

@ -4,3 +4,10 @@ server:
spring:
application:
name: fund-report
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP

View File

@ -2,8 +2,10 @@ package com.fundplatform.req;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ReqApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,109 @@
package com.fundplatform.req.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.common.core.Result;
import com.fundplatform.req.dto.FundRequestDTO;
import com.fundplatform.req.service.FundRequestService;
import com.fundplatform.req.vo.FundRequestVO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
/**
* 用款申请Controller
*/
@RestController
@RequestMapping("/api/v1/req/fund-request")
public class FundRequestController {
private final FundRequestService fundRequestService;
public FundRequestController(FundRequestService fundRequestService) {
this.fundRequestService = fundRequestService;
}
/**
* 创建用款申请
*/
@PostMapping
public Result<Long> create(@Valid @RequestBody FundRequestDTO dto) {
Long id = fundRequestService.createRequest(dto);
return Result.success(id);
}
/**
* 更新用款申请
*/
@PutMapping
public Result<Boolean> update(@Valid @RequestBody FundRequestDTO dto) {
boolean result = fundRequestService.updateRequest(dto);
return Result.success(result);
}
/**
* 根据ID查询用款申请
*/
@GetMapping("/{id}")
public Result<FundRequestVO> getById(@PathVariable Long id) {
FundRequestVO vo = fundRequestService.getRequestById(id);
return Result.success(vo);
}
/**
* 分页查询用款申请
*/
@GetMapping("/page")
public Result<Page<FundRequestVO>> page(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String title,
@RequestParam(required = false) Integer requestType,
@RequestParam(required = false) Integer approvalStatus) {
Page<FundRequestVO> page = fundRequestService.pageRequests(pageNum, pageSize, title, requestType, approvalStatus);
return Result.success(page);
}
/**
* 删除用款申请
*/
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
boolean result = fundRequestService.deleteRequest(id);
return Result.success(result);
}
/**
* 提交审批
*/
@PostMapping("/{id}/submit")
public Result<Boolean> submit(@PathVariable Long id) {
boolean result = fundRequestService.submitApproval(id);
return Result.success(result);
}
/**
* 审批通过
*/
@PutMapping("/{id}/approve")
public Result<Boolean> approve(@PathVariable Long id, @RequestParam(required = false) String comment) {
boolean result = fundRequestService.approve(id, comment);
return Result.success(result);
}
/**
* 审批拒绝
*/
@PutMapping("/{id}/reject")
public Result<Boolean> reject(@PathVariable Long id, @RequestParam(required = false) String comment) {
boolean result = fundRequestService.reject(id, comment);
return Result.success(result);
}
/**
* 撤回申请
*/
@PutMapping("/{id}/withdraw")
public Result<Boolean> withdraw(@PathVariable Long id) {
boolean result = fundRequestService.withdraw(id);
return Result.success(result);
}
}

View File

@ -0,0 +1,223 @@
package com.fundplatform.req.data.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fundplatform.common.core.BaseEntity;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 用款申请实体
*/
@TableName("fund_request")
public class FundRequest extends BaseEntity {
/** 申请单号 */
private String requestNo;
/** 申请标题 */
private String title;
/** 申请金额 */
private BigDecimal amount;
/** 币种 */
private String currency;
/** 用款类型(1-日常报销 2-项目付款 3-预付款 4-其他) */
private Integer requestType;
/** 收款单位 */
private String payeeName;
/** 收款银行 */
private String payeeBank;
/** 收款账号 */
private String payeeAccount;
/** 用途说明 */
private String purpose;
/** 项目ID */
private Long projectId;
/** 客户ID */
private Long customerId;
/** 申请日期 */
private LocalDateTime requestDate;
/** 期望付款日期 */
private LocalDateTime expectedPayDate;
/** 审批状态(0-待审批 1-审批中 2-审批通过 3-审批拒绝 4-已撤回) */
private Integer approvalStatus;
/** 当前审批节点 */
private Integer currentNode;
/** 审批人ID */
private Long approverId;
/** 审批时间 */
private LocalDateTime approvalTime;
/** 审批意见 */
private String approvalComment;
/** 附件URL */
private String attachments;
public String getRequestNo() {
return requestNo;
}
public void setRequestNo(String requestNo) {
this.requestNo = requestNo;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public Integer getRequestType() {
return requestType;
}
public void setRequestType(Integer requestType) {
this.requestType = requestType;
}
public String getPayeeName() {
return payeeName;
}
public void setPayeeName(String payeeName) {
this.payeeName = payeeName;
}
public String getPayeeBank() {
return payeeBank;
}
public void setPayeeBank(String payeeBank) {
this.payeeBank = payeeBank;
}
public String getPayeeAccount() {
return payeeAccount;
}
public void setPayeeAccount(String payeeAccount) {
this.payeeAccount = payeeAccount;
}
public String getPurpose() {
return purpose;
}
public void setPurpose(String purpose) {
this.purpose = purpose;
}
public Long getProjectId() {
return projectId;
}
public void setProjectId(Long projectId) {
this.projectId = projectId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public LocalDateTime getRequestDate() {
return requestDate;
}
public void setRequestDate(LocalDateTime requestDate) {
this.requestDate = requestDate;
}
public LocalDateTime getExpectedPayDate() {
return expectedPayDate;
}
public void setExpectedPayDate(LocalDateTime expectedPayDate) {
this.expectedPayDate = expectedPayDate;
}
public Integer getApprovalStatus() {
return approvalStatus;
}
public void setApprovalStatus(Integer approvalStatus) {
this.approvalStatus = approvalStatus;
}
public Integer getCurrentNode() {
return currentNode;
}
public void setCurrentNode(Integer currentNode) {
this.currentNode = currentNode;
}
public Long getApproverId() {
return approverId;
}
public void setApproverId(Long approverId) {
this.approverId = approverId;
}
public LocalDateTime getApprovalTime() {
return approvalTime;
}
public void setApprovalTime(LocalDateTime approvalTime) {
this.approvalTime = approvalTime;
}
public String getApprovalComment() {
return approvalComment;
}
public void setApprovalComment(String approvalComment) {
this.approvalComment = approvalComment;
}
public String getAttachments() {
return attachments;
}
public void setAttachments(String attachments) {
this.attachments = attachments;
}
}

View File

@ -0,0 +1,12 @@
package com.fundplatform.req.data.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.fundplatform.req.data.entity.FundRequest;
import org.apache.ibatis.annotations.Mapper;
/**
* 用款申请Mapper
*/
@Mapper
public interface FundRequestMapper extends BaseMapper<FundRequest> {
}

View File

@ -0,0 +1,15 @@
package com.fundplatform.req.data.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fundplatform.req.data.entity.FundRequest;
import com.fundplatform.req.data.mapper.FundRequestMapper;
import org.springframework.stereotype.Service;
/**
* 用款申请数据服务
* 职责:仅负责数据访问数据封装和转换,不承载业务逻辑
*/
@Service
public class FundRequestDataService extends ServiceImpl<FundRequestMapper, FundRequest> implements IService<FundRequest> {
}

View File

@ -0,0 +1,165 @@
package com.fundplatform.req.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 用款申请DTO
*/
public class FundRequestDTO {
private Long id;
@NotBlank(message = "申请标题不能为空")
@Size(max = 200, message = "申请标题不能超过200个字符")
private String title;
@NotNull(message = "申请金额不能为空")
@Positive(message = "申请金额必须大于0")
private BigDecimal amount;
private String currency = "CNY";
@NotNull(message = "用款类型不能为空")
private Integer requestType;
@NotBlank(message = "收款单位不能为空")
@Size(max = 100, message = "收款单位不能超过100个字符")
private String payeeName;
@Size(max = 100, message = "收款银行不能超过100个字符")
private String payeeBank;
@Size(max = 50, message = "收款账号不能超过50个字符")
private String payeeAccount;
@Size(max = 500, message = "用途说明不能超过500个字符")
private String purpose;
private Long projectId;
private Long customerId;
private LocalDateTime expectedPayDate;
private String attachments;
private String remark;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public Integer getRequestType() {
return requestType;
}
public void setRequestType(Integer requestType) {
this.requestType = requestType;
}
public String getPayeeName() {
return payeeName;
}
public void setPayeeName(String payeeName) {
this.payeeName = payeeName;
}
public String getPayeeBank() {
return payeeBank;
}
public void setPayeeBank(String payeeBank) {
this.payeeBank = payeeBank;
}
public String getPayeeAccount() {
return payeeAccount;
}
public void setPayeeAccount(String payeeAccount) {
this.payeeAccount = payeeAccount;
}
public String getPurpose() {
return purpose;
}
public void setPurpose(String purpose) {
this.purpose = purpose;
}
public Long getProjectId() {
return projectId;
}
public void setProjectId(Long projectId) {
this.projectId = projectId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public LocalDateTime getExpectedPayDate() {
return expectedPayDate;
}
public void setExpectedPayDate(LocalDateTime expectedPayDate) {
this.expectedPayDate = expectedPayDate;
}
public String getAttachments() {
return attachments;
}
public void setAttachments(String attachments) {
this.attachments = attachments;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

@ -0,0 +1,89 @@
package com.fundplatform.req.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.req.dto.FundRequestDTO;
import com.fundplatform.req.vo.FundRequestVO;
/**
* 用款申请服务接口
*/
public interface FundRequestService {
/**
* 创建用款申请
*
* @param dto 用款申请DTO
* @return 申请ID
*/
Long createRequest(FundRequestDTO dto);
/**
* 更新用款申请
*
* @param dto 用款申请DTO
* @return 是否成功
*/
boolean updateRequest(FundRequestDTO dto);
/**
* 根据ID查询用款申请
*
* @param id 申请ID
* @return 用款申请VO
*/
FundRequestVO getRequestById(Long id);
/**
* 分页查询用款申请
*
* @param pageNum 页码
* @param pageSize 每页大小
* @param title 标题模糊查询
* @param requestType 用款类型
* @param approvalStatus 审批状态
* @return 分页数据
*/
Page<FundRequestVO> pageRequests(int pageNum, int pageSize, String title, Integer requestType, Integer approvalStatus);
/**
* 删除用款申请
*
* @param id 申请ID
* @return 是否成功
*/
boolean deleteRequest(Long id);
/**
* 提交审批
*
* @param id 申请ID
* @return 是否成功
*/
boolean submitApproval(Long id);
/**
* 审批通过
*
* @param id 申请ID
* @param comment 审批意见
* @return 是否成功
*/
boolean approve(Long id, String comment);
/**
* 审批拒绝
*
* @param id 申请ID
* @param comment 审批意见
* @return 是否成功
*/
boolean reject(Long id, String comment);
/**
* 撤回申请
*
* @param id 申请ID
* @return 是否成功
*/
boolean withdraw(Long id);
}

View File

@ -0,0 +1,301 @@
package com.fundplatform.req.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.req.data.entity.FundRequest;
import com.fundplatform.req.data.service.FundRequestDataService;
import com.fundplatform.req.dto.FundRequestDTO;
import com.fundplatform.req.service.FundRequestService;
import com.fundplatform.req.vo.FundRequestVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 用款申请服务实现
*/
@Service
public class FundRequestServiceImpl implements FundRequestService {
private static final Logger log = LoggerFactory.getLogger(FundRequestServiceImpl.class);
private static final AtomicInteger counter = new AtomicInteger(1);
private final FundRequestDataService requestDataService;
public FundRequestServiceImpl(FundRequestDataService requestDataService) {
this.requestDataService = requestDataService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createRequest(FundRequestDTO dto) {
FundRequest request = new FundRequest();
request.setRequestNo(generateRequestNo());
request.setTitle(dto.getTitle());
request.setAmount(dto.getAmount());
request.setCurrency(dto.getCurrency() != null ? dto.getCurrency() : "CNY");
request.setRequestType(dto.getRequestType());
request.setPayeeName(dto.getPayeeName());
request.setPayeeBank(dto.getPayeeBank());
request.setPayeeAccount(dto.getPayeeAccount());
request.setPurpose(dto.getPurpose());
request.setProjectId(dto.getProjectId());
request.setCustomerId(dto.getCustomerId());
request.setRequestDate(LocalDateTime.now());
request.setExpectedPayDate(dto.getExpectedPayDate());
request.setApprovalStatus(0); // 待审批
request.setCurrentNode(1);
request.setAttachments(dto.getAttachments());
request.setDeleted(0);
request.setCreatedTime(LocalDateTime.now());
requestDataService.save(request);
log.info("创建用款申请成功: id={}, requestNo={}", request.getId(), request.getRequestNo());
return request.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateRequest(FundRequestDTO dto) {
if (dto.getId() == null) {
throw new RuntimeException("申请ID不能为空");
}
FundRequest existing = requestDataService.getById(dto.getId());
if (existing == null || existing.getDeleted() == 1) {
throw new RuntimeException("用款申请不存在");
}
// 只有待审批状态才能修改
if (existing.getApprovalStatus() != 0) {
throw new RuntimeException("当前状态不允许修改");
}
FundRequest request = new FundRequest();
request.setId(dto.getId());
request.setTitle(dto.getTitle());
request.setAmount(dto.getAmount());
request.setCurrency(dto.getCurrency());
request.setRequestType(dto.getRequestType());
request.setPayeeName(dto.getPayeeName());
request.setPayeeBank(dto.getPayeeBank());
request.setPayeeAccount(dto.getPayeeAccount());
request.setPurpose(dto.getPurpose());
request.setProjectId(dto.getProjectId());
request.setCustomerId(dto.getCustomerId());
request.setExpectedPayDate(dto.getExpectedPayDate());
request.setAttachments(dto.getAttachments());
request.setUpdatedTime(LocalDateTime.now());
boolean result = requestDataService.updateById(request);
log.info("更新用款申请: id={}, result={}", dto.getId(), result);
return result;
}
@Override
public FundRequestVO getRequestById(Long id) {
FundRequest request = requestDataService.getById(id);
if (request == null || request.getDeleted() == 1) {
throw new RuntimeException("用款申请不存在");
}
return convertToVO(request);
}
@Override
public Page<FundRequestVO> pageRequests(int pageNum, int pageSize, String title, Integer requestType, Integer approvalStatus) {
Page<FundRequest> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<FundRequest> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(FundRequest::getDeleted, 0);
if (StringUtils.hasText(title)) {
wrapper.like(FundRequest::getTitle, title);
}
if (requestType != null) {
wrapper.eq(FundRequest::getRequestType, requestType);
}
if (approvalStatus != null) {
wrapper.eq(FundRequest::getApprovalStatus, approvalStatus);
}
wrapper.orderByDesc(FundRequest::getCreatedTime);
Page<FundRequest> requestPage = requestDataService.page(page, wrapper);
Page<FundRequestVO> voPage = new Page<>(requestPage.getCurrent(), requestPage.getSize(), requestPage.getTotal());
voPage.setRecords(requestPage.getRecords().stream().map(this::convertToVO).toList());
return voPage;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteRequest(Long id) {
LambdaUpdateWrapper<FundRequest> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundRequest::getId, id);
wrapper.set(FundRequest::getDeleted, 1);
wrapper.set(FundRequest::getUpdatedTime, LocalDateTime.now());
boolean result = requestDataService.update(wrapper);
log.info("删除用款申请: id={}", id);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean submitApproval(Long id) {
FundRequest request = requestDataService.getById(id);
if (request == null || request.getDeleted() == 1) {
throw new RuntimeException("用款申请不存在");
}
if (request.getApprovalStatus() != 0) {
throw new RuntimeException("当前状态不允许提交审批");
}
LambdaUpdateWrapper<FundRequest> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundRequest::getId, id);
wrapper.set(FundRequest::getApprovalStatus, 1); // 审批中
wrapper.set(FundRequest::getUpdatedTime, LocalDateTime.now());
boolean result = requestDataService.update(wrapper);
log.info("提交用款申请审批: id={}", id);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean approve(Long id, String comment) {
FundRequest request = requestDataService.getById(id);
if (request == null || request.getDeleted() == 1) {
throw new RuntimeException("用款申请不存在");
}
if (request.getApprovalStatus() != 1) {
throw new RuntimeException("当前状态不允许审批");
}
LambdaUpdateWrapper<FundRequest> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundRequest::getId, id);
wrapper.set(FundRequest::getApprovalStatus, 2); // 审批通过
wrapper.set(FundRequest::getApprovalTime, LocalDateTime.now());
wrapper.set(FundRequest::getApprovalComment, comment);
wrapper.set(FundRequest::getUpdatedTime, LocalDateTime.now());
boolean result = requestDataService.update(wrapper);
log.info("审批通过用款申请: id={}", id);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean reject(Long id, String comment) {
FundRequest request = requestDataService.getById(id);
if (request == null || request.getDeleted() == 1) {
throw new RuntimeException("用款申请不存在");
}
if (request.getApprovalStatus() != 1) {
throw new RuntimeException("当前状态不允许审批");
}
LambdaUpdateWrapper<FundRequest> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundRequest::getId, id);
wrapper.set(FundRequest::getApprovalStatus, 3); // 审批拒绝
wrapper.set(FundRequest::getApprovalTime, LocalDateTime.now());
wrapper.set(FundRequest::getApprovalComment, comment);
wrapper.set(FundRequest::getUpdatedTime, LocalDateTime.now());
boolean result = requestDataService.update(wrapper);
log.info("审批拒绝用款申请: id={}", id);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean withdraw(Long id) {
FundRequest request = requestDataService.getById(id);
if (request == null || request.getDeleted() == 1) {
throw new RuntimeException("用款申请不存在");
}
if (request.getApprovalStatus() != 1) {
throw new RuntimeException("当前状态不允许撤回");
}
LambdaUpdateWrapper<FundRequest> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(FundRequest::getId, id);
wrapper.set(FundRequest::getApprovalStatus, 4); // 已撤回
wrapper.set(FundRequest::getUpdatedTime, LocalDateTime.now());
boolean result = requestDataService.update(wrapper);
log.info("撤回用款申请: id={}", id);
return result;
}
/**
* 生成申请单号
*/
private String generateRequestNo() {
String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
int seq = counter.getAndIncrement();
return String.format("REQ%s%04d", dateStr, seq);
}
/**
* 转换为VO
*/
private FundRequestVO convertToVO(FundRequest request) {
FundRequestVO vo = new FundRequestVO();
vo.setId(request.getId());
vo.setRequestNo(request.getRequestNo());
vo.setTitle(request.getTitle());
vo.setAmount(request.getAmount());
vo.setCurrency(request.getCurrency());
vo.setRequestType(request.getRequestType());
vo.setRequestTypeName(getRequestTypeName(request.getRequestType()));
vo.setPayeeName(request.getPayeeName());
vo.setPayeeBank(request.getPayeeBank());
vo.setPayeeAccount(request.getPayeeAccount());
vo.setPurpose(request.getPurpose());
vo.setProjectId(request.getProjectId());
vo.setCustomerId(request.getCustomerId());
vo.setRequestDate(request.getRequestDate());
vo.setExpectedPayDate(request.getExpectedPayDate());
vo.setApprovalStatus(request.getApprovalStatus());
vo.setApprovalStatusName(getApprovalStatusName(request.getApprovalStatus()));
vo.setCurrentNode(request.getCurrentNode());
vo.setApproverId(request.getApproverId());
vo.setApprovalTime(request.getApprovalTime());
vo.setApprovalComment(request.getApprovalComment());
vo.setAttachments(request.getAttachments());
vo.setTenantId(request.getTenantId());
vo.setCreatedBy(request.getCreatedBy());
vo.setCreatedTime(request.getCreatedTime());
vo.setUpdatedTime(request.getUpdatedTime());
return vo;
}
private String getRequestTypeName(Integer type) {
if (type == null) return "";
switch (type) {
case 1: return "日常报销";
case 2: return "项目付款";
case 3: return "预付款";
case 4: return "其他";
default: return "";
}
}
private String getApprovalStatusName(Integer status) {
if (status == null) return "";
switch (status) {
case 0: return "待审批";
case 1: return "审批中";
case 2: return "审批通过";
case 3: return "审批拒绝";
case 4: return "已撤回";
default: return "";
}
}
}

View File

@ -0,0 +1,281 @@
package com.fundplatform.req.vo;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 用款申请VO
*/
public class FundRequestVO {
private Long id;
private String requestNo;
private String title;
private BigDecimal amount;
private String currency;
private Integer requestType;
private String requestTypeName;
private String payeeName;
private String payeeBank;
private String payeeAccount;
private String purpose;
private Long projectId;
private String projectName;
private Long customerId;
private String customerName;
private LocalDateTime requestDate;
private LocalDateTime expectedPayDate;
private Integer approvalStatus;
private String approvalStatusName;
private Integer currentNode;
private Long approverId;
private String approverName;
private LocalDateTime approvalTime;
private String approvalComment;
private String attachments;
private Long tenantId;
private Long createdBy;
private String createdByName;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRequestNo() {
return requestNo;
}
public void setRequestNo(String requestNo) {
this.requestNo = requestNo;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
public Integer getRequestType() {
return requestType;
}
public void setRequestType(Integer requestType) {
this.requestType = requestType;
}
public String getRequestTypeName() {
return requestTypeName;
}
public void setRequestTypeName(String requestTypeName) {
this.requestTypeName = requestTypeName;
}
public String getPayeeName() {
return payeeName;
}
public void setPayeeName(String payeeName) {
this.payeeName = payeeName;
}
public String getPayeeBank() {
return payeeBank;
}
public void setPayeeBank(String payeeBank) {
this.payeeBank = payeeBank;
}
public String getPayeeAccount() {
return payeeAccount;
}
public void setPayeeAccount(String payeeAccount) {
this.payeeAccount = payeeAccount;
}
public String getPurpose() {
return purpose;
}
public void setPurpose(String purpose) {
this.purpose = purpose;
}
public Long getProjectId() {
return projectId;
}
public void setProjectId(Long projectId) {
this.projectId = projectId;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public LocalDateTime getRequestDate() {
return requestDate;
}
public void setRequestDate(LocalDateTime requestDate) {
this.requestDate = requestDate;
}
public LocalDateTime getExpectedPayDate() {
return expectedPayDate;
}
public void setExpectedPayDate(LocalDateTime expectedPayDate) {
this.expectedPayDate = expectedPayDate;
}
public Integer getApprovalStatus() {
return approvalStatus;
}
public void setApprovalStatus(Integer approvalStatus) {
this.approvalStatus = approvalStatus;
}
public String getApprovalStatusName() {
return approvalStatusName;
}
public void setApprovalStatusName(String approvalStatusName) {
this.approvalStatusName = approvalStatusName;
}
public Integer getCurrentNode() {
return currentNode;
}
public void setCurrentNode(Integer currentNode) {
this.currentNode = currentNode;
}
public Long getApproverId() {
return approverId;
}
public void setApproverId(Long approverId) {
this.approverId = approverId;
}
public String getApproverName() {
return approverName;
}
public void setApproverName(String approverName) {
this.approverName = approverName;
}
public LocalDateTime getApprovalTime() {
return approvalTime;
}
public void setApprovalTime(LocalDateTime approvalTime) {
this.approvalTime = approvalTime;
}
public String getApprovalComment() {
return approvalComment;
}
public void setApprovalComment(String approvalComment) {
this.approvalComment = approvalComment;
}
public String getAttachments() {
return attachments;
}
public void setAttachments(String attachments) {
this.attachments = attachments;
}
public Long getTenantId() {
return tenantId;
}
public void setTenantId(Long tenantId) {
this.tenantId = tenantId;
}
public Long getCreatedBy() {
return createdBy;
}
public void setCreatedBy(Long createdBy) {
this.createdBy = createdBy;
}
public String getCreatedByName() {
return createdByName;
}
public void setCreatedByName(String createdByName) {
this.createdByName = createdByName;
}
public LocalDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(LocalDateTime createdTime) {
this.createdTime = createdTime;
}
public LocalDateTime getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(LocalDateTime updatedTime) {
this.updatedTime = updatedTime;
}
}

View File

@ -4,3 +4,10 @@ server:
spring:
application:
name: fund-req
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP

View File

@ -3,11 +3,13 @@ package com.fundplatform.sys;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* 系统服务启动类
*/
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.fundplatform.sys.data.mapper")
public class SysApplication {

View File

@ -0,0 +1,66 @@
package com.fundplatform.sys.controller;
import com.fundplatform.common.core.Result;
import com.fundplatform.sys.dto.DeptDTO;
import com.fundplatform.sys.service.DeptService;
import com.fundplatform.sys.vo.DeptVO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 部门管理Controller
*/
@RestController
@RequestMapping("/api/v1/sys/dept")
public class DeptController {
private final DeptService deptService;
public DeptController(DeptService deptService) {
this.deptService = deptService;
}
@PostMapping
public Result<Long> create(@Valid @RequestBody DeptDTO dto) {
Long deptId = deptService.createDept(dto);
return Result.success(deptId);
}
@PutMapping
public Result<Boolean> update(@Valid @RequestBody DeptDTO dto) {
boolean result = deptService.updateDept(dto);
return Result.success(result);
}
@GetMapping("/{id}")
public Result<DeptVO> getById(@PathVariable Long id) {
DeptVO vo = deptService.getDeptById(id);
return Result.success(vo);
}
@GetMapping("/tree")
public Result<List<DeptVO>> getTree() {
List<DeptVO> tree = deptService.getDeptTree();
return Result.success(tree);
}
@GetMapping("/list")
public Result<List<DeptVO>> listAll() {
List<DeptVO> list = deptService.listAllDepts();
return Result.success(list);
}
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
boolean result = deptService.deleteDept(id);
return Result.success(result);
}
@PutMapping("/{id}/status")
public Result<Boolean> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
boolean result = deptService.updateStatus(id, status);
return Result.success(result);
}
}

View File

@ -0,0 +1,60 @@
package com.fundplatform.sys.controller;
import com.fundplatform.common.core.Result;
import com.fundplatform.sys.dto.MenuDTO;
import com.fundplatform.sys.service.MenuService;
import com.fundplatform.sys.vo.MenuVO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 菜单管理Controller
*/
@RestController
@RequestMapping("/api/v1/sys/menu")
public class MenuController {
private final MenuService menuService;
public MenuController(MenuService menuService) {
this.menuService = menuService;
}
@PostMapping
public Result<Long> create(@Valid @RequestBody MenuDTO dto) {
Long menuId = menuService.createMenu(dto);
return Result.success(menuId);
}
@PutMapping
public Result<Boolean> update(@Valid @RequestBody MenuDTO dto) {
boolean result = menuService.updateMenu(dto);
return Result.success(result);
}
@GetMapping("/{id}")
public Result<MenuVO> getById(@PathVariable Long id) {
MenuVO vo = menuService.getMenuById(id);
return Result.success(vo);
}
@GetMapping("/tree")
public Result<List<MenuVO>> getTree() {
List<MenuVO> tree = menuService.getMenuTree();
return Result.success(tree);
}
@GetMapping("/user/{userId}")
public Result<List<MenuVO>> getUserTree(@PathVariable Long userId) {
List<MenuVO> tree = menuService.getUserMenuTree(userId);
return Result.success(tree);
}
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
boolean result = menuService.deleteMenu(id);
return Result.success(result);
}
}

View File

@ -0,0 +1,77 @@
package com.fundplatform.sys.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.common.core.Result;
import com.fundplatform.sys.dto.RoleDTO;
import com.fundplatform.sys.service.RoleService;
import com.fundplatform.sys.vo.RoleVO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 角色管理Controller
*/
@RestController
@RequestMapping("/api/v1/sys/role")
public class RoleController {
private final RoleService roleService;
public RoleController(RoleService roleService) {
this.roleService = roleService;
}
@PostMapping
public Result<Long> create(@Valid @RequestBody RoleDTO dto) {
Long roleId = roleService.createRole(dto);
return Result.success(roleId);
}
@PutMapping
public Result<Boolean> update(@Valid @RequestBody RoleDTO dto) {
boolean result = roleService.updateRole(dto);
return Result.success(result);
}
@GetMapping("/{id}")
public Result<RoleVO> getById(@PathVariable Long id) {
RoleVO vo = roleService.getRoleById(id);
return Result.success(vo);
}
@GetMapping("/page")
public Result<Page<RoleVO>> page(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String roleName,
@RequestParam(required = false) Integer status) {
Page<RoleVO> page = roleService.pageRoles(pageNum, pageSize, roleName, status);
return Result.success(page);
}
@GetMapping("/list")
public Result<List<RoleVO>> listAll() {
List<RoleVO> list = roleService.listAllRoles();
return Result.success(list);
}
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
boolean result = roleService.deleteRole(id);
return Result.success(result);
}
@PutMapping("/{id}/status")
public Result<Boolean> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
boolean result = roleService.updateStatus(id, status);
return Result.success(result);
}
@PostMapping("/{id}/menus")
public Result<Boolean> assignMenus(@PathVariable Long id, @RequestBody List<Long> menuIds) {
boolean result = roleService.assignMenus(id, menuIds);
return Result.success(result);
}
}

View File

@ -0,0 +1,100 @@
package com.fundplatform.sys.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.common.core.Result;
import com.fundplatform.sys.dto.UserDTO;
import com.fundplatform.sys.service.UserService;
import com.fundplatform.sys.vo.UserVO;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
/**
* 用户管理Controller
*/
@RestController
@RequestMapping("/api/v1/sys/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
/**
* 创建用户
*/
@PostMapping
public Result<Long> create(@Valid @RequestBody UserDTO dto) {
Long userId = userService.createUser(dto);
return Result.success(userId);
}
/**
* 更新用户
*/
@PutMapping
public Result<Boolean> update(@Valid @RequestBody UserDTO dto) {
boolean result = userService.updateUser(dto);
return Result.success(result);
}
/**
* 根据ID查询用户
*/
@GetMapping("/{id}")
public Result<UserVO> getById(@PathVariable Long id) {
UserVO vo = userService.getUserById(id);
return Result.success(vo);
}
/**
* 分页查询用户
*/
@GetMapping("/page")
public Result<Page<UserVO>> page(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String username,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) Long deptId) {
Page<UserVO> page = userService.pageUsers(pageNum, pageSize, username, status, deptId);
return Result.success(page);
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
boolean result = userService.deleteUser(id);
return Result.success(result);
}
/**
* 批量删除用户
*/
@DeleteMapping("/batch")
public Result<Boolean> batchDelete(@RequestBody Long[] ids) {
boolean result = userService.batchDeleteUsers(ids);
return Result.success(result);
}
/**
* 重置密码
*/
@PutMapping("/{id}/reset-password")
public Result<Boolean> resetPassword(@PathVariable Long id) {
boolean result = userService.resetPassword(id);
return Result.success(result);
}
/**
* 更新用户状态
*/
@PutMapping("/{id}/status")
public Result<Boolean> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
boolean result = userService.updateStatus(id, status);
return Result.success(result);
}
}

View File

@ -0,0 +1,115 @@
package com.fundplatform.sys.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
/**
* 部门DTO
*/
public class DeptDTO {
private Long id;
private Long parentId;
@NotBlank(message = "部门编码不能为空")
@Size(max = 50, message = "部门编码不能超过50个字符")
private String deptCode;
@NotBlank(message = "部门名称不能为空")
@Size(max = 50, message = "部门名称不能超过50个字符")
private String deptName;
@Size(max = 50, message = "部门负责人不能超过50个字符")
private String deptLeader;
private String phone;
private String email;
private Integer sortOrder;
private Integer status;
private String remark;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getDeptCode() {
return deptCode;
}
public void setDeptCode(String deptCode) {
this.deptCode = deptCode;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getDeptLeader() {
return deptLeader;
}
public void setDeptLeader(String deptLeader) {
this.deptLeader = deptLeader;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

@ -0,0 +1,136 @@
package com.fundplatform.sys.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
/**
* 菜单DTO
*/
public class MenuDTO {
private Long id;
private Long parentId;
@NotBlank(message = "菜单名称不能为空")
@Size(max = 50, message = "菜单名称不能超过50个字符")
private String menuName;
private Integer menuType;
@Size(max = 200, message = "菜单路径不能超过200个字符")
private String menuPath;
@Size(max = 100, message = "菜单图标不能超过100个字符")
private String menuIcon;
@Size(max = 200, message = "组件路径不能超过200个字符")
private String component;
@Size(max = 100, message = "权限标识不能超过100个字符")
private String permission;
private Integer sortOrder;
private Integer visible;
private Integer status;
private String remark;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public Integer getMenuType() {
return menuType;
}
public void setMenuType(Integer menuType) {
this.menuType = menuType;
}
public String getMenuPath() {
return menuPath;
}
public void setMenuPath(String menuPath) {
this.menuPath = menuPath;
}
public String getMenuIcon() {
return menuIcon;
}
public void setMenuIcon(String menuIcon) {
this.menuIcon = menuIcon;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Integer getVisible() {
return visible;
}
public void setVisible(Integer visible) {
this.visible = visible;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

@ -0,0 +1,84 @@
package com.fundplatform.sys.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
/**
* 角色DTO
*/
public class RoleDTO {
private Long id;
@NotBlank(message = "角色编码不能为空")
@Size(max = 50, message = "角色编码不能超过50个字符")
private String roleCode;
@NotBlank(message = "角色名称不能为空")
@Size(max = 50, message = "角色名称不能超过50个字符")
private String roleName;
private Integer dataScope;
private Integer status;
private Integer sortOrder;
private String remark;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Integer getDataScope() {
return dataScope;
}
public void setDataScope(Integer dataScope) {
this.dataScope = dataScope;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

@ -0,0 +1,118 @@
package com.fundplatform.sys.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
/**
* 用户DTO创建/更新
*/
public class UserDTO {
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@Size(min = 6, max = 100, message = "密码长度必须在6-100个字符之间")
private String password;
@Size(max = 50, message = "真实姓名不能超过50个字符")
private String realName;
@Pattern(regexp = "^$|^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Pattern(regexp = "^$|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "邮箱格式不正确")
private String email;
private Long deptId;
private Integer status;
private String avatar;
private String remark;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Long getDeptId() {
return deptId;
}
public void setDeptId(Long deptId) {
this.deptId = deptId;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

@ -0,0 +1,26 @@
package com.fundplatform.sys.service;
import com.fundplatform.sys.dto.DeptDTO;
import com.fundplatform.sys.vo.DeptVO;
import java.util.List;
/**
* 部门服务接口
*/
public interface DeptService {
Long createDept(DeptDTO dto);
boolean updateDept(DeptDTO dto);
DeptVO getDeptById(Long id);
List<DeptVO> getDeptTree();
List<DeptVO> listAllDepts();
boolean deleteDept(Long id);
boolean updateStatus(Long id, Integer status);
}

View File

@ -0,0 +1,42 @@
package com.fundplatform.sys.service;
import com.fundplatform.sys.dto.MenuDTO;
import com.fundplatform.sys.vo.MenuVO;
import java.util.List;
/**
* 菜单服务接口
*/
public interface MenuService {
/**
* 创建菜单
*/
Long createMenu(MenuDTO dto);
/**
* 更新菜单
*/
boolean updateMenu(MenuDTO dto);
/**
* 根据ID查询菜单
*/
MenuVO getMenuById(Long id);
/**
* 查询菜单树
*/
List<MenuVO> getMenuTree();
/**
* 查询用户菜单树
*/
List<MenuVO> getUserMenuTree(Long userId);
/**
* 删除菜单
*/
boolean deleteMenu(Long id);
}

View File

@ -0,0 +1,53 @@
package com.fundplatform.sys.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.sys.dto.RoleDTO;
import com.fundplatform.sys.vo.RoleVO;
import java.util.List;
/**
* 角色服务接口
*/
public interface RoleService {
/**
* 创建角色
*/
Long createRole(RoleDTO dto);
/**
* 更新角色
*/
boolean updateRole(RoleDTO dto);
/**
* 根据ID查询角色
*/
RoleVO getRoleById(Long id);
/**
* 分页查询角色
*/
Page<RoleVO> pageRoles(int pageNum, int pageSize, String roleName, Integer status);
/**
* 查询所有角色
*/
List<RoleVO> listAllRoles();
/**
* 删除角色
*/
boolean deleteRole(Long id);
/**
* 更新角色状态
*/
boolean updateStatus(Long id, Integer status);
/**
* 分配菜单权限
*/
boolean assignMenus(Long roleId, List<Long> menuIds);
}

View File

@ -0,0 +1,80 @@
package com.fundplatform.sys.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.sys.dto.UserDTO;
import com.fundplatform.sys.vo.UserVO;
/**
* 用户服务接口
*/
public interface UserService {
/**
* 创建用户
*
* @param dto 用户DTO
* @return 用户ID
*/
Long createUser(UserDTO dto);
/**
* 更新用户
*
* @param dto 用户DTO
* @return 是否成功
*/
boolean updateUser(UserDTO dto);
/**
* 根据ID查询用户
*
* @param id 用户ID
* @return 用户VO
*/
UserVO getUserById(Long id);
/**
* 分页查询用户
*
* @param pageNum 页码
* @param pageSize 每页大小
* @param username 用户名模糊查询
* @param status 状态
* @param deptId 部门ID
* @return 分页数据
*/
Page<UserVO> pageUsers(int pageNum, int pageSize, String username, Integer status, Long deptId);
/**
* 删除用户
*
* @param id 用户ID
* @return 是否成功
*/
boolean deleteUser(Long id);
/**
* 批量删除用户
*
* @param ids 用户ID列表
* @return 是否成功
*/
boolean batchDeleteUsers(Long[] ids);
/**
* 重置密码
*
* @param id 用户ID
* @return 是否成功
*/
boolean resetPassword(Long id);
/**
* 更新用户状态
*
* @param id 用户ID
* @param status 状态
* @return 是否成功
*/
boolean updateStatus(Long id, Integer status);
}

View File

@ -0,0 +1,180 @@
package com.fundplatform.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fundplatform.sys.data.entity.SysDept;
import com.fundplatform.sys.data.service.SysDeptDataService;
import com.fundplatform.sys.dto.DeptDTO;
import com.fundplatform.sys.service.DeptService;
import com.fundplatform.sys.vo.DeptVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 部门服务实现
*/
@Service
public class DeptServiceImpl implements DeptService {
private static final Logger log = LoggerFactory.getLogger(DeptServiceImpl.class);
private final SysDeptDataService deptDataService;
public DeptServiceImpl(SysDeptDataService deptDataService) {
this.deptDataService = deptDataService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createDept(DeptDTO dto) {
LambdaQueryWrapper<SysDept> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysDept::getDeptCode, dto.getDeptCode());
wrapper.eq(SysDept::getDeleted, 0);
if (deptDataService.count(wrapper) > 0) {
throw new RuntimeException("部门编码已存在");
}
SysDept dept = new SysDept();
dept.setParentId(dto.getParentId() != null ? dto.getParentId() : 0L);
dept.setDeptCode(dto.getDeptCode());
dept.setDeptName(dto.getDeptName());
dept.setDeptLeader(dto.getDeptLeader());
dept.setPhone(dto.getPhone());
dept.setEmail(dto.getEmail());
dept.setSortOrder(dto.getSortOrder() != null ? dto.getSortOrder() : 0);
dept.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
dept.setDeleted(0);
dept.setCreatedTime(LocalDateTime.now());
deptDataService.save(dept);
log.info("创建部门成功: deptId={}, deptName={}", dept.getId(), dept.getDeptName());
return dept.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateDept(DeptDTO dto) {
if (dto.getId() == null) {
throw new RuntimeException("部门ID不能为空");
}
SysDept existing = deptDataService.getById(dto.getId());
if (existing == null || existing.getDeleted() == 1) {
throw new RuntimeException("部门不存在");
}
SysDept dept = new SysDept();
dept.setId(dto.getId());
dept.setParentId(dto.getParentId());
dept.setDeptName(dto.getDeptName());
dept.setDeptLeader(dto.getDeptLeader());
dept.setPhone(dto.getPhone());
dept.setEmail(dto.getEmail());
dept.setSortOrder(dto.getSortOrder());
dept.setStatus(dto.getStatus());
dept.setUpdatedTime(LocalDateTime.now());
boolean result = deptDataService.updateById(dept);
log.info("更新部门: deptId={}", dto.getId());
return result;
}
@Override
public DeptVO getDeptById(Long id) {
SysDept dept = deptDataService.getById(id);
if (dept == null || dept.getDeleted() == 1) {
throw new RuntimeException("部门不存在");
}
return convertToVO(dept);
}
@Override
public List<DeptVO> getDeptTree() {
LambdaQueryWrapper<SysDept> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysDept::getDeleted, 0);
wrapper.eq(SysDept::getStatus, 1);
wrapper.orderByAsc(SysDept::getSortOrder);
List<SysDept> depts = deptDataService.list(wrapper);
return buildDeptTree(depts, 0L);
}
@Override
public List<DeptVO> listAllDepts() {
LambdaQueryWrapper<SysDept> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysDept::getDeleted, 0);
wrapper.orderByAsc(SysDept::getSortOrder);
return deptDataService.list(wrapper).stream().map(this::convertToVO).toList();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteDept(Long id) {
LambdaQueryWrapper<SysDept> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysDept::getParentId, id);
wrapper.eq(SysDept::getDeleted, 0);
if (deptDataService.count(wrapper) > 0) {
throw new RuntimeException("存在子部门,不能删除");
}
LambdaUpdateWrapper<SysDept> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(SysDept::getId, id);
updateWrapper.set(SysDept::getDeleted, 1);
updateWrapper.set(SysDept::getUpdatedTime, LocalDateTime.now());
boolean result = deptDataService.update(updateWrapper);
log.info("删除部门: deptId={}", id);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateStatus(Long id, Integer status) {
LambdaUpdateWrapper<SysDept> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SysDept::getId, id);
wrapper.set(SysDept::getStatus, status);
wrapper.set(SysDept::getUpdatedTime, LocalDateTime.now());
boolean result = deptDataService.update(wrapper);
log.info("更新部门状态: deptId={}, status={}", id, status);
return result;
}
private List<DeptVO> buildDeptTree(List<SysDept> depts, Long parentId) {
List<DeptVO> tree = new ArrayList<>();
Map<Long, List<SysDept>> deptMap = depts.stream()
.collect(Collectors.groupingBy(SysDept::getParentId));
List<SysDept> rootDepts = deptMap.getOrDefault(parentId, new ArrayList<>());
for (SysDept dept : rootDepts) {
DeptVO vo = convertToVO(dept);
vo.setChildren(buildDeptTree(depts, dept.getId()));
tree.add(vo);
}
return tree;
}
private DeptVO convertToVO(SysDept dept) {
DeptVO vo = new DeptVO();
vo.setId(dept.getId());
vo.setParentId(dept.getParentId());
vo.setDeptCode(dept.getDeptCode());
vo.setDeptName(dept.getDeptName());
vo.setDeptLeader(dept.getDeptLeader());
vo.setPhone(dept.getPhone());
vo.setEmail(dept.getEmail());
vo.setSortOrder(dept.getSortOrder());
vo.setStatus(dept.getStatus());
vo.setTenantId(dept.getTenantId());
vo.setCreatedTime(dept.getCreatedTime());
vo.setUpdatedTime(dept.getUpdatedTime());
return vo;
}
}

View File

@ -0,0 +1,178 @@
package com.fundplatform.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fundplatform.sys.data.entity.SysMenu;
import com.fundplatform.sys.data.service.SysMenuDataService;
import com.fundplatform.sys.dto.MenuDTO;
import com.fundplatform.sys.service.MenuService;
import com.fundplatform.sys.vo.MenuVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 菜单服务实现
*/
@Service
public class MenuServiceImpl implements MenuService {
private static final Logger log = LoggerFactory.getLogger(MenuServiceImpl.class);
private final SysMenuDataService menuDataService;
public MenuServiceImpl(SysMenuDataService menuDataService) {
this.menuDataService = menuDataService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createMenu(MenuDTO dto) {
SysMenu menu = new SysMenu();
menu.setParentId(dto.getParentId() != null ? dto.getParentId() : 0L);
menu.setMenuName(dto.getMenuName());
menu.setMenuType(dto.getMenuType() != null ? dto.getMenuType() : 1);
menu.setMenuPath(dto.getMenuPath());
menu.setMenuIcon(dto.getMenuIcon());
menu.setComponent(dto.getComponent());
menu.setPermission(dto.getPermission());
menu.setSortOrder(dto.getSortOrder() != null ? dto.getSortOrder() : 0);
menu.setVisible(dto.getVisible() != null ? dto.getVisible() : 1);
menu.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
menu.setDeleted(0);
menu.setCreatedTime(LocalDateTime.now());
menuDataService.save(menu);
log.info("创建菜单成功: menuId={}, menuName={}", menu.getId(), menu.getMenuName());
return menu.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateMenu(MenuDTO dto) {
if (dto.getId() == null) {
throw new RuntimeException("菜单ID不能为空");
}
SysMenu existing = menuDataService.getById(dto.getId());
if (existing == null || existing.getDeleted() == 1) {
throw new RuntimeException("菜单不存在");
}
SysMenu menu = new SysMenu();
menu.setId(dto.getId());
menu.setParentId(dto.getParentId());
menu.setMenuName(dto.getMenuName());
menu.setMenuType(dto.getMenuType());
menu.setMenuPath(dto.getMenuPath());
menu.setMenuIcon(dto.getMenuIcon());
menu.setComponent(dto.getComponent());
menu.setPermission(dto.getPermission());
menu.setSortOrder(dto.getSortOrder());
menu.setVisible(dto.getVisible());
menu.setStatus(dto.getStatus());
menu.setUpdatedTime(LocalDateTime.now());
boolean result = menuDataService.updateById(menu);
log.info("更新菜单: menuId={}", dto.getId());
return result;
}
@Override
public MenuVO getMenuById(Long id) {
SysMenu menu = menuDataService.getById(id);
if (menu == null || menu.getDeleted() == 1) {
throw new RuntimeException("菜单不存在");
}
return convertToVO(menu);
}
@Override
public List<MenuVO> getMenuTree() {
LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysMenu::getDeleted, 0);
wrapper.eq(SysMenu::getStatus, 1);
wrapper.orderByAsc(SysMenu::getSortOrder);
List<SysMenu> menus = menuDataService.list(wrapper);
return buildMenuTree(menus, 0L);
}
@Override
public List<MenuVO> getUserMenuTree(Long userId) {
// TODO: 根据用户角色查询菜单
return getMenuTree();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteMenu(Long id) {
// 检查是否有子菜单
LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysMenu::getParentId, id);
wrapper.eq(SysMenu::getDeleted, 0);
if (menuDataService.count(wrapper) > 0) {
throw new RuntimeException("存在子菜单,不能删除");
}
LambdaUpdateWrapper<SysMenu> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(SysMenu::getId, id);
updateWrapper.set(SysMenu::getDeleted, 1);
updateWrapper.set(SysMenu::getUpdatedTime, LocalDateTime.now());
boolean result = menuDataService.update(updateWrapper);
log.info("删除菜单: menuId={}", id);
return result;
}
private List<MenuVO> buildMenuTree(List<SysMenu> menus, Long parentId) {
List<MenuVO> tree = new ArrayList<>();
Map<Long, List<SysMenu>> menuMap = menus.stream()
.collect(Collectors.groupingBy(SysMenu::getParentId));
List<SysMenu> rootMenus = menuMap.getOrDefault(parentId, new ArrayList<>());
for (SysMenu menu : rootMenus) {
MenuVO vo = convertToVO(menu);
vo.setChildren(buildMenuTree(menus, menu.getId()));
tree.add(vo);
}
return tree;
}
private MenuVO convertToVO(SysMenu menu) {
MenuVO vo = new MenuVO();
vo.setId(menu.getId());
vo.setParentId(menu.getParentId());
vo.setMenuName(menu.getMenuName());
vo.setMenuType(menu.getMenuType());
vo.setMenuTypeName(getMenuTypeName(menu.getMenuType()));
vo.setMenuPath(menu.getMenuPath());
vo.setMenuIcon(menu.getMenuIcon());
vo.setComponent(menu.getComponent());
vo.setPermission(menu.getPermission());
vo.setSortOrder(menu.getSortOrder());
vo.setVisible(menu.getVisible());
vo.setStatus(menu.getStatus());
vo.setTenantId(menu.getTenantId());
vo.setCreatedTime(menu.getCreatedTime());
vo.setUpdatedTime(menu.getUpdatedTime());
return vo;
}
private String getMenuTypeName(Integer menuType) {
if (menuType == null) return "";
switch (menuType) {
case 1: return "目录";
case 2: return "菜单";
case 3: return "按钮";
default: return "";
}
}
}

View File

@ -0,0 +1,180 @@
package com.fundplatform.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.sys.data.entity.SysRole;
import com.fundplatform.sys.data.service.SysRoleDataService;
import com.fundplatform.sys.dto.RoleDTO;
import com.fundplatform.sys.service.RoleService;
import com.fundplatform.sys.vo.RoleVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 角色服务实现
*/
@Service
public class RoleServiceImpl implements RoleService {
private static final Logger log = LoggerFactory.getLogger(RoleServiceImpl.class);
private final SysRoleDataService roleDataService;
public RoleServiceImpl(SysRoleDataService roleDataService) {
this.roleDataService = roleDataService;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createRole(RoleDTO dto) {
// 检查角色编码是否存在
LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysRole::getRoleCode, dto.getRoleCode());
wrapper.eq(SysRole::getDeleted, 0);
if (roleDataService.count(wrapper) > 0) {
throw new RuntimeException("角色编码已存在");
}
SysRole role = new SysRole();
role.setRoleCode(dto.getRoleCode());
role.setRoleName(dto.getRoleName());
role.setDataScope(dto.getDataScope() != null ? dto.getDataScope() : 1);
role.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
role.setSortOrder(dto.getSortOrder());
role.setDeleted(0);
role.setCreatedTime(LocalDateTime.now());
roleDataService.save(role);
log.info("创建角色成功: roleId={}, roleCode={}", role.getId(), role.getRoleCode());
return role.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateRole(RoleDTO dto) {
if (dto.getId() == null) {
throw new RuntimeException("角色ID不能为空");
}
SysRole existing = roleDataService.getById(dto.getId());
if (existing == null || existing.getDeleted() == 1) {
throw new RuntimeException("角色不存在");
}
SysRole role = new SysRole();
role.setId(dto.getId());
role.setRoleName(dto.getRoleName());
role.setDataScope(dto.getDataScope());
role.setStatus(dto.getStatus());
role.setSortOrder(dto.getSortOrder());
role.setUpdatedTime(LocalDateTime.now());
boolean result = roleDataService.updateById(role);
log.info("更新角色: roleId={}, result={}", dto.getId(), result);
return result;
}
@Override
public RoleVO getRoleById(Long id) {
SysRole role = roleDataService.getById(id);
if (role == null || role.getDeleted() == 1) {
throw new RuntimeException("角色不存在");
}
return convertToVO(role);
}
@Override
public Page<RoleVO> pageRoles(int pageNum, int pageSize, String roleName, Integer status) {
Page<SysRole> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysRole::getDeleted, 0);
if (StringUtils.hasText(roleName)) {
wrapper.like(SysRole::getRoleName, roleName);
}
if (status != null) {
wrapper.eq(SysRole::getStatus, status);
}
wrapper.orderByAsc(SysRole::getSortOrder);
Page<SysRole> rolePage = roleDataService.page(page, wrapper);
Page<RoleVO> voPage = new Page<>(rolePage.getCurrent(), rolePage.getSize(), rolePage.getTotal());
voPage.setRecords(rolePage.getRecords().stream().map(this::convertToVO).toList());
return voPage;
}
@Override
public List<RoleVO> listAllRoles() {
LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysRole::getDeleted, 0);
wrapper.eq(SysRole::getStatus, 1);
wrapper.orderByAsc(SysRole::getSortOrder);
return roleDataService.list(wrapper).stream().map(this::convertToVO).toList();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteRole(Long id) {
LambdaUpdateWrapper<SysRole> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SysRole::getId, id);
wrapper.set(SysRole::getDeleted, 1);
wrapper.set(SysRole::getUpdatedTime, LocalDateTime.now());
boolean result = roleDataService.update(wrapper);
log.info("删除角色: roleId={}", id);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateStatus(Long id, Integer status) {
LambdaUpdateWrapper<SysRole> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SysRole::getId, id);
wrapper.set(SysRole::getStatus, status);
wrapper.set(SysRole::getUpdatedTime, LocalDateTime.now());
boolean result = roleDataService.update(wrapper);
log.info("更新角色状态: roleId={}, status={}", id, status);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean assignMenus(Long roleId, List<Long> menuIds) {
// TODO: 实现角色菜单关联
log.info("分配角色菜单: roleId={}, menuIds={}", roleId, menuIds);
return true;
}
private RoleVO convertToVO(SysRole role) {
RoleVO vo = new RoleVO();
vo.setId(role.getId());
vo.setRoleCode(role.getRoleCode());
vo.setRoleName(role.getRoleName());
vo.setDataScope(role.getDataScope());
vo.setDataScopeName(getDataScopeName(role.getDataScope()));
vo.setStatus(role.getStatus());
vo.setSortOrder(role.getSortOrder());
vo.setTenantId(role.getTenantId());
vo.setCreatedTime(role.getCreatedTime());
vo.setUpdatedTime(role.getUpdatedTime());
return vo;
}
private String getDataScopeName(Integer dataScope) {
if (dataScope == null) return "";
switch (dataScope) {
case 1: return "全部数据";
case 2: return "本部门数据";
case 3: return "本部门及下级数据";
case 4: return "仅本人数据";
default: return "";
}
}
}

View File

@ -0,0 +1,230 @@
package com.fundplatform.sys.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fundplatform.sys.data.entity.SysDept;
import com.fundplatform.sys.data.entity.SysUser;
import com.fundplatform.sys.data.service.SysDeptDataService;
import com.fundplatform.sys.data.service.SysUserDataService;
import com.fundplatform.sys.dto.UserDTO;
import com.fundplatform.sys.service.UserService;
import com.fundplatform.sys.vo.UserVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.Arrays;
/**
* 用户服务实现
*/
@Service
public class UserServiceImpl implements UserService {
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
private final SysUserDataService userDataService;
private final SysDeptDataService deptDataService;
private final BCryptPasswordEncoder passwordEncoder;
public UserServiceImpl(SysUserDataService userDataService, SysDeptDataService deptDataService) {
this.userDataService = userDataService;
this.deptDataService = deptDataService;
this.passwordEncoder = new BCryptPasswordEncoder();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createUser(UserDTO dto) {
// 检查用户名是否存在
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUsername, dto.getUsername());
wrapper.eq(SysUser::getDeleted, 0);
if (userDataService.count(wrapper) > 0) {
throw new RuntimeException("用户名已存在");
}
// 创建用户
SysUser user = new SysUser();
user.setUsername(dto.getUsername());
user.setPassword(passwordEncoder.encode(dto.getPassword()));
user.setRealName(dto.getRealName());
user.setPhone(dto.getPhone());
user.setEmail(dto.getEmail());
user.setDeptId(dto.getDeptId());
user.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
user.setAvatar(dto.getAvatar());
user.setRemark(dto.getRemark());
user.setDeleted(0);
user.setCreatedTime(LocalDateTime.now());
userDataService.save(user);
log.info("创建用户成功: userId={}, username={}", user.getId(), user.getUsername());
return user.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateUser(UserDTO dto) {
if (dto.getId() == null) {
throw new RuntimeException("用户ID不能为空");
}
// 检查用户是否存在
SysUser existingUser = userDataService.getById(dto.getId());
if (existingUser == null || existingUser.getDeleted() == 1) {
throw new RuntimeException("用户不存在");
}
// 如果修改了用户名检查是否重复
if (StringUtils.hasText(dto.getUsername()) && !dto.getUsername().equals(existingUser.getUsername())) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUsername, dto.getUsername());
wrapper.eq(SysUser::getDeleted, 0);
wrapper.ne(SysUser::getId, dto.getId());
if (userDataService.count(wrapper) > 0) {
throw new RuntimeException("用户名已存在");
}
}
// 更新用户
SysUser user = new SysUser();
user.setId(dto.getId());
if (StringUtils.hasText(dto.getUsername())) {
user.setUsername(dto.getUsername());
}
if (StringUtils.hasText(dto.getPassword())) {
user.setPassword(passwordEncoder.encode(dto.getPassword()));
}
user.setRealName(dto.getRealName());
user.setPhone(dto.getPhone());
user.setEmail(dto.getEmail());
user.setDeptId(dto.getDeptId());
user.setStatus(dto.getStatus());
user.setAvatar(dto.getAvatar());
user.setRemark(dto.getRemark());
user.setUpdatedTime(LocalDateTime.now());
boolean result = userDataService.updateById(user);
log.info("更新用户: userId={}, result={}", dto.getId(), result);
return result;
}
@Override
public UserVO getUserById(Long id) {
SysUser user = userDataService.getById(id);
if (user == null || user.getDeleted() == 1) {
throw new RuntimeException("用户不存在");
}
return convertToVO(user);
}
@Override
public Page<UserVO> pageUsers(int pageNum, int pageSize, String username, Integer status, Long deptId) {
Page<SysUser> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getDeleted, 0);
if (StringUtils.hasText(username)) {
wrapper.like(SysUser::getUsername, username);
}
if (status != null) {
wrapper.eq(SysUser::getStatus, status);
}
if (deptId != null) {
wrapper.eq(SysUser::getDeptId, deptId);
}
wrapper.orderByDesc(SysUser::getCreatedTime);
Page<SysUser> userPage = userDataService.page(page, wrapper);
// 转换为VO
Page<UserVO> voPage = new Page<>(userPage.getCurrent(), userPage.getSize(), userPage.getTotal());
voPage.setRecords(userPage.getRecords().stream().map(this::convertToVO).toList());
return voPage;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteUser(Long id) {
LambdaUpdateWrapper<SysUser> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SysUser::getId, id);
wrapper.set(SysUser::getDeleted, 1);
wrapper.set(SysUser::getUpdatedTime, LocalDateTime.now());
boolean result = userDataService.update(wrapper);
log.info("删除用户: userId={}, result={}", id, result);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean batchDeleteUsers(Long[] ids) {
if (ids == null || ids.length == 0) {
return false;
}
LambdaUpdateWrapper<SysUser> wrapper = new LambdaUpdateWrapper<>();
wrapper.in(SysUser::getId, Arrays.asList(ids));
wrapper.set(SysUser::getDeleted, 1);
wrapper.set(SysUser::getUpdatedTime, LocalDateTime.now());
boolean result = userDataService.update(wrapper);
log.info("批量删除用户: ids={}, result={}", Arrays.toString(ids), result);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean resetPassword(Long id) {
String defaultPassword = "123456";
LambdaUpdateWrapper<SysUser> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SysUser::getId, id);
wrapper.set(SysUser::getPassword, passwordEncoder.encode(defaultPassword));
wrapper.set(SysUser::getUpdatedTime, LocalDateTime.now());
boolean result = userDataService.update(wrapper);
log.info("重置用户密码: userId={}", id);
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateStatus(Long id, Integer status) {
LambdaUpdateWrapper<SysUser> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(SysUser::getId, id);
wrapper.set(SysUser::getStatus, status);
wrapper.set(SysUser::getUpdatedTime, LocalDateTime.now());
boolean result = userDataService.update(wrapper);
log.info("更新用户状态: userId={}, status={}", id, status);
return result;
}
/**
* 转换为VO
*/
private UserVO convertToVO(SysUser user) {
UserVO vo = new UserVO();
vo.setId(user.getId());
vo.setUsername(user.getUsername());
vo.setRealName(user.getRealName());
vo.setPhone(user.getPhone());
vo.setEmail(user.getEmail());
vo.setDeptId(user.getDeptId());
vo.setStatus(user.getStatus());
vo.setAvatar(user.getAvatar());
vo.setTenantId(user.getTenantId());
vo.setCreatedTime(user.getCreatedTime());
vo.setUpdatedTime(user.getUpdatedTime());
// 查询部门名称
if (user.getDeptId() != null) {
SysDept dept = deptDataService.getById(user.getDeptId());
if (dept != null) {
vo.setDeptName(dept.getDeptName());
}
}
return vo;
}
}

View File

@ -0,0 +1,139 @@
package com.fundplatform.sys.vo;
import java.time.LocalDateTime;
import java.util.List;
/**
* 部门VO
*/
public class DeptVO {
private Long id;
private Long parentId;
private String parentName;
private String deptCode;
private String deptName;
private String deptLeader;
private String phone;
private String email;
private Integer sortOrder;
private Integer status;
private Long tenantId;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
// 子部门
private List<DeptVO> children;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
public String getDeptCode() {
return deptCode;
}
public void setDeptCode(String deptCode) {
this.deptCode = deptCode;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getDeptLeader() {
return deptLeader;
}
public void setDeptLeader(String deptLeader) {
this.deptLeader = deptLeader;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getTenantId() {
return tenantId;
}
public void setTenantId(Long tenantId) {
this.tenantId = tenantId;
}
public LocalDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(LocalDateTime createdTime) {
this.createdTime = createdTime;
}
public LocalDateTime getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(LocalDateTime updatedTime) {
this.updatedTime = updatedTime;
}
public List<DeptVO> getChildren() {
return children;
}
public void setChildren(List<DeptVO> children) {
this.children = children;
}
}

View File

@ -0,0 +1,157 @@
package com.fundplatform.sys.vo;
import java.time.LocalDateTime;
import java.util.List;
/**
* 菜单VO
*/
public class MenuVO {
private Long id;
private Long parentId;
private String menuName;
private Integer menuType;
private String menuTypeName;
private String menuPath;
private String menuIcon;
private String component;
private String permission;
private Integer sortOrder;
private Integer visible;
private Integer status;
private Long tenantId;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
// 子菜单
private List<MenuVO> children;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public Integer getMenuType() {
return menuType;
}
public void setMenuType(Integer menuType) {
this.menuType = menuType;
}
public String getMenuTypeName() {
return menuTypeName;
}
public void setMenuTypeName(String menuTypeName) {
this.menuTypeName = menuTypeName;
}
public String getMenuPath() {
return menuPath;
}
public void setMenuPath(String menuPath) {
this.menuPath = menuPath;
}
public String getMenuIcon() {
return menuIcon;
}
public void setMenuIcon(String menuIcon) {
this.menuIcon = menuIcon;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Integer getVisible() {
return visible;
}
public void setVisible(Integer visible) {
this.visible = visible;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getTenantId() {
return tenantId;
}
public void setTenantId(Long tenantId) {
this.tenantId = tenantId;
}
public LocalDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(LocalDateTime createdTime) {
this.createdTime = createdTime;
}
public LocalDateTime getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(LocalDateTime updatedTime) {
this.updatedTime = updatedTime;
}
public List<MenuVO> getChildren() {
return children;
}
public void setChildren(List<MenuVO> children) {
this.children = children;
}
}

View File

@ -0,0 +1,100 @@
package com.fundplatform.sys.vo;
import java.time.LocalDateTime;
/**
* 角色VO
*/
public class RoleVO {
private Long id;
private String roleCode;
private String roleName;
private Integer dataScope;
private String dataScopeName;
private Integer status;
private Integer sortOrder;
private Long tenantId;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleCode() {
return roleCode;
}
public void setRoleCode(String roleCode) {
this.roleCode = roleCode;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Integer getDataScope() {
return dataScope;
}
public void setDataScope(Integer dataScope) {
this.dataScope = dataScope;
}
public String getDataScopeName() {
return dataScopeName;
}
public void setDataScopeName(String dataScopeName) {
this.dataScopeName = dataScopeName;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getSortOrder() {
return sortOrder;
}
public void setSortOrder(Integer sortOrder) {
this.sortOrder = sortOrder;
}
public Long getTenantId() {
return tenantId;
}
public void setTenantId(Long tenantId) {
this.tenantId = tenantId;
}
public LocalDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(LocalDateTime createdTime) {
this.createdTime = createdTime;
}
public LocalDateTime getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(LocalDateTime updatedTime) {
this.updatedTime = updatedTime;
}
}

View File

@ -0,0 +1,118 @@
package com.fundplatform.sys.vo;
import java.time.LocalDateTime;
/**
* 用户VO返回视图
*/
public class UserVO {
private Long id;
private String username;
private String realName;
private String phone;
private String email;
private Long deptId;
private String deptName;
private Integer status;
private String avatar;
private Long tenantId;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Long getDeptId() {
return deptId;
}
public void setDeptId(Long deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public Long getTenantId() {
return tenantId;
}
public void setTenantId(Long tenantId) {
this.tenantId = tenantId;
}
public LocalDateTime getCreatedTime() {
return createdTime;
}
public void setCreatedTime(LocalDateTime createdTime) {
this.createdTime = createdTime;
}
public LocalDateTime getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(LocalDateTime updatedTime) {
this.updatedTime = updatedTime;
}
}

View File

@ -5,6 +5,13 @@ spring:
application:
name: fund-sys
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: fund-platform
group: DEFAULT_GROUP
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/fund_sys?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
@ -15,6 +22,21 @@ spring:
minimum-idle: 5
connection-timeout: 30000
# Redis配置
data:
redis:
host: localhost
port: 6379
password: zjf@123456
database: 0
timeout: 10000
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.fundplatform.sys.data.entity

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 定义日志文件路径 -->
<property name="LOG_PATH" value="./logs"/>
<property name="APP_NAME" value="fund-sys"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- INFO级别日志文件 -->
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}/info.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- ERROR级别日志文件 -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}/error.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId:-}] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- JSON格式日志(用于ELK采集) -->
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}/json.log</file>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app_name":"${APP_NAME}"}</customFields>
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
<includeMdcKeyName>tenantId</includeMdcKeyName>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}/json-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- 开发环境 -->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_INFO"/>
<appender-ref ref="FILE_ERROR"/>
<appender-ref ref="JSON_FILE"/>
</root>
</springProfile>
<!-- 默认配置 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_INFO"/>
<appender-ref ref="FILE_ERROR"/>
</root>
</configuration>

23
pom.xml
View File

@ -35,8 +35,31 @@
<properties>
<java.version>21</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2023.0.0.0-RC1</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 项目内自定义 Maven 仓库配置,避免依赖外部 settings.xml 中的私服配置 -->
<repositories>
<!-- 官方中央仓库 -->