初始化项目
This commit is contained in:
parent
45a1702948
commit
934b8aad40
|
|
@ -26,6 +26,7 @@
|
||||||
<swagger.version>3.0.0</swagger.version>
|
<swagger.version>3.0.0</swagger.version>
|
||||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||||
<hutool.version>5.8.23</hutool.version>
|
<hutool.version>5.8.23</hutool.version>
|
||||||
|
<poi.version>5.2.5</poi.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
@ -50,9 +51,9 @@
|
||||||
|
|
||||||
<!-- MySQL Driver -->
|
<!-- MySQL Driver -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
<version>8.0.33</version>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Druid 数据源 -->
|
<!-- Druid 数据源 -->
|
||||||
|
|
@ -101,6 +102,24 @@
|
||||||
<version>${hutool.version}</version>
|
<version>${hutool.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Apache POI for Excel processing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi</artifactId>
|
||||||
|
<version>${poi.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml</artifactId>
|
||||||
|
<version>${poi.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Security Crypto (密码加密) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Lombok -->
|
<!-- Lombok -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
|
|
@ -160,7 +179,6 @@
|
||||||
<version>3.3.0</version>
|
<version>3.3.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<configLocation>checkstyle.xml</configLocation>
|
<configLocation>checkstyle.xml</configLocation>
|
||||||
<encoding>UTF-8</encoding>
|
|
||||||
<consoleOutput>true</consoleOutput>
|
<consoleOutput>true</consoleOutput>
|
||||||
<failsOnError>false</failsOnError>
|
<failsOnError>false</failsOnError>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
||||||
|
|
@ -23,70 +23,70 @@ public class Constants {
|
||||||
/**
|
/**
|
||||||
* 客户状态 - 可报备
|
* 客户状态 - 可报备
|
||||||
*/
|
*/
|
||||||
public static final Integer CUSTOMER_STATUS_AVAILABLE = 0;
|
public static final int CUSTOMER_STATUS_AVAILABLE = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户状态 - 保护中
|
* 客户状态 - 保护中
|
||||||
*/
|
*/
|
||||||
public static final Integer CUSTOMER_STATUS_PROTECTED = 1;
|
public static final int CUSTOMER_STATUS_PROTECTED = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户状态 - 已失效
|
* 客户状态 - 已失效
|
||||||
*/
|
*/
|
||||||
public static final Integer CUSTOMER_STATUS_EXPIRED = 2;
|
public static final int CUSTOMER_STATUS_EXPIRED = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报备状态 - 待审核
|
* 报备状态 - 待审核
|
||||||
*/
|
*/
|
||||||
public static final Integer REPORT_STATUS_PENDING = 0;
|
public static final int REPORT_STATUS_PENDING = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报备状态 - 已通过
|
* 报备状态 - 已通过
|
||||||
*/
|
*/
|
||||||
public static final Integer REPORT_STATUS_APPROVED = 1;
|
public static final int REPORT_STATUS_APPROVED = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报备状态 - 已驳回
|
* 报备状态 - 已驳回
|
||||||
*/
|
*/
|
||||||
public static final Integer REPORT_STATUS_REJECTED = 2;
|
public static final int REPORT_STATUS_REJECTED = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报备状态 - 已失效
|
* 报备状态 - 已失效
|
||||||
*/
|
*/
|
||||||
public static final Integer REPORT_STATUS_EXPIRED = 3;
|
public static final int REPORT_STATUS_EXPIRED = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户角色 - 管理员
|
* 用户角色 - 管理员
|
||||||
*/
|
*/
|
||||||
public static final Integer USER_ROLE_ADMIN = 0;
|
public static final int USER_ROLE_ADMIN = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户角色 - 经销商用户
|
* 用户角色 - 经销商用户
|
||||||
*/
|
*/
|
||||||
public static final Integer USER_ROLE_DEALER = 1;
|
public static final int USER_ROLE_DEALER = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户状态 - 禁用
|
* 用户状态 - 禁用
|
||||||
*/
|
*/
|
||||||
public static final Integer USER_STATUS_DISABLED = 0;
|
public static final int USER_STATUS_DISABLED = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户状态 - 启用
|
* 用户状态 - 启用
|
||||||
*/
|
*/
|
||||||
public static final Integer USER_STATUS_ENABLED = 1;
|
public static final int USER_STATUS_ENABLED = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 经销商状态 - 禁用
|
* 经销商状态 - 禁用
|
||||||
*/
|
*/
|
||||||
public static final Integer DEALER_STATUS_DISABLED = 0;
|
public static final int DEALER_STATUS_DISABLED = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 经销商状态 - 启用
|
* 经销商状态 - 启用
|
||||||
*/
|
*/
|
||||||
public static final Integer DEALER_STATUS_ENABLED = 1;
|
public static final int DEALER_STATUS_ENABLED = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认保护期天数
|
* 默认保护期天数
|
||||||
*/
|
*/
|
||||||
public static final Integer DEFAULT_PROTECT_DAYS = 90;
|
public static final int DEFAULT_PROTECT_DAYS = 90;
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package com.bycrm.controller;
|
package com.bycrm.controller;
|
||||||
|
|
||||||
import com.bycrm.common.Result;
|
import com.bycrm.common.Result;
|
||||||
|
import com.bycrm.dto.ChangePasswordDTO;
|
||||||
import com.bycrm.dto.LoginDTO;
|
import com.bycrm.dto.LoginDTO;
|
||||||
|
import com.bycrm.dto.ResetPasswordDTO;
|
||||||
import com.bycrm.service.UserService;
|
import com.bycrm.service.UserService;
|
||||||
import com.bycrm.vo.UserInfoVO;
|
import com.bycrm.vo.UserInfoVO;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
|
|
@ -54,6 +56,30 @@ public class AuthController {
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码
|
||||||
|
*/
|
||||||
|
@ApiOperation("修改密码")
|
||||||
|
@PostMapping("/change-password")
|
||||||
|
public Result<Void> changePassword(
|
||||||
|
@RequestBody ChangePasswordDTO dto,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
String token = getTokenFromRequest(request);
|
||||||
|
Long userId = userService.getCurrentUser(token).getUserId();
|
||||||
|
userService.changePassword(userId, dto);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置密码(管理员功能)
|
||||||
|
*/
|
||||||
|
@ApiOperation("重置密码")
|
||||||
|
@PostMapping("/reset-password")
|
||||||
|
public Result<Void> resetPassword(@RequestBody ResetPasswordDTO dto) {
|
||||||
|
userService.resetPassword(dto);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从请求中获取 Token
|
* 从请求中获取 Token
|
||||||
*/
|
*/
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.bycrm.controller;
|
||||||
|
|
||||||
|
import com.bycrm.common.Result;
|
||||||
|
import com.bycrm.dto.DashboardStatisticsDTO;
|
||||||
|
import com.bycrm.entity.User;
|
||||||
|
import com.bycrm.mapper.CustomerMapper;
|
||||||
|
import com.bycrm.mapper.DealerMapper;
|
||||||
|
import com.bycrm.mapper.UserMapper;
|
||||||
|
import com.bycrm.service.ReportService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页统计控制器
|
||||||
|
*/
|
||||||
|
@Api(tags = "首页统计")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/dashboard")
|
||||||
|
public class DashboardController {
|
||||||
|
|
||||||
|
private final CustomerMapper customerMapper;
|
||||||
|
private final DealerMapper dealerMapper;
|
||||||
|
private final ReportService reportService;
|
||||||
|
private final UserMapper userMapper;
|
||||||
|
|
||||||
|
public DashboardController(CustomerMapper customerMapper,
|
||||||
|
DealerMapper dealerMapper,
|
||||||
|
ReportService reportService,
|
||||||
|
UserMapper userMapper) {
|
||||||
|
this.customerMapper = customerMapper;
|
||||||
|
this.dealerMapper = dealerMapper;
|
||||||
|
this.reportService = reportService;
|
||||||
|
this.userMapper = userMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取首页统计数据
|
||||||
|
*/
|
||||||
|
@ApiOperation("获取首页统计数据")
|
||||||
|
@GetMapping("/statistics")
|
||||||
|
public Result<DashboardStatisticsDTO> getStatistics(HttpServletRequest request) {
|
||||||
|
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||||
|
User currentUser = userMapper.selectById(currentUserId);
|
||||||
|
|
||||||
|
DashboardStatisticsDTO stats = new DashboardStatisticsDTO();
|
||||||
|
|
||||||
|
// 客户总数(所有人看到的客户总数一样)
|
||||||
|
Long customerCount = customerMapper.countPage(null, null, null);
|
||||||
|
stats.setCustomerCount(customerCount);
|
||||||
|
|
||||||
|
// 报备总数和待审核数量(根据角色过滤)
|
||||||
|
Long dealerId = null;
|
||||||
|
if (currentUser.getRole() != 0) {
|
||||||
|
// 经销商用户,只统计自己的
|
||||||
|
dealerId = currentUser.getDealerId();
|
||||||
|
}
|
||||||
|
// 管理员用户,dealerId为null,统计所有
|
||||||
|
|
||||||
|
Long reportCount = reportService.countByDealerId(dealerId);
|
||||||
|
stats.setReportCount(reportCount);
|
||||||
|
|
||||||
|
Long pendingCount = reportService.countPendingByDealerId(dealerId);
|
||||||
|
stats.setPendingCount(pendingCount);
|
||||||
|
|
||||||
|
// 经销商总数(仅管理员可见)
|
||||||
|
if (currentUser.getRole() == 0) {
|
||||||
|
Long dealerCount = dealerMapper.count();
|
||||||
|
stats.setDealerCount(dealerCount);
|
||||||
|
} else {
|
||||||
|
stats.setDealerCount(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,7 +32,7 @@ public class DealerController {
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public Result<List<Dealer>> getDealerList(
|
public Result<List<Dealer>> getDealerList(
|
||||||
@ApiParam("经销商名称") @RequestParam(required = false) String name,
|
@ApiParam("经销商名称") @RequestParam(required = false) String name,
|
||||||
@ApiParam("经销商编码") @RequestParam(required = false) String code,
|
@ApiParam("经销商账号") @RequestParam(required = false) String code,
|
||||||
@ApiParam("状态") @RequestParam(required = false) Integer status) {
|
@ApiParam("状态") @RequestParam(required = false) Integer status) {
|
||||||
List<Dealer> dealers = dealerService.getDealerList(name, code, status);
|
List<Dealer> dealers = dealerService.getDealerList(name, code, status);
|
||||||
return Result.success(dealers);
|
return Result.success(dealers);
|
||||||
128
backend/src/main/java/com/bycrm/controller/SchoolController.java
Normal file
128
backend/src/main/java/com/bycrm/controller/SchoolController.java
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
package com.bycrm.controller;
|
||||||
|
|
||||||
|
import com.bycrm.common.Result;
|
||||||
|
import com.bycrm.dto.SchoolDTO;
|
||||||
|
import com.bycrm.service.SchoolService;
|
||||||
|
import com.bycrm.util.SchoolImporter;
|
||||||
|
import com.bycrm.vo.SchoolVO;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import io.swagger.annotations.ApiParam;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校控制器
|
||||||
|
*/
|
||||||
|
@Api(tags = "学校管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/school")
|
||||||
|
public class SchoolController {
|
||||||
|
|
||||||
|
private final SchoolService schoolService;
|
||||||
|
|
||||||
|
@Value("${project.root:./}")
|
||||||
|
private String projectRoot;
|
||||||
|
|
||||||
|
public SchoolController(SchoolService schoolService) {
|
||||||
|
this.schoolService = schoolService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据名称搜索学校(用于自动完成)
|
||||||
|
*/
|
||||||
|
@ApiOperation("根据名称搜索学校")
|
||||||
|
@GetMapping("/search")
|
||||||
|
public Result<List<SchoolVO>> searchByName(
|
||||||
|
@ApiParam("关键词") @RequestParam String keyword) {
|
||||||
|
List<SchoolVO> schools = schoolService.searchByName(keyword);
|
||||||
|
return Result.success(schools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有学校
|
||||||
|
*/
|
||||||
|
@ApiOperation("查询所有学校")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Result<List<SchoolVO>> findAll() {
|
||||||
|
List<SchoolVO> schools = schoolService.findAll();
|
||||||
|
return Result.success(schools);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建学校
|
||||||
|
*/
|
||||||
|
@ApiOperation("创建学校")
|
||||||
|
@PostMapping
|
||||||
|
public Result<Long> create(@RequestBody SchoolDTO dto) {
|
||||||
|
Long id = schoolService.create(dto);
|
||||||
|
return Result.success(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量导入学校
|
||||||
|
*/
|
||||||
|
@ApiOperation("批量导入学校")
|
||||||
|
@PostMapping("/batch")
|
||||||
|
public Result<Integer> batchImport(@RequestBody List<SchoolDTO> list) {
|
||||||
|
int count = schoolService.batchImport(list);
|
||||||
|
return Result.success(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从Excel文件导入学校数据
|
||||||
|
*/
|
||||||
|
@ApiOperation("从Excel文件导入学校数据")
|
||||||
|
@PostMapping("/import")
|
||||||
|
public Result<Integer> importFromExcel(@RequestParam("file") MultipartFile file) {
|
||||||
|
try {
|
||||||
|
// 保存临时文件
|
||||||
|
File tempFile = File.createTempFile("school_import_", ".xls");
|
||||||
|
file.transferTo(tempFile);
|
||||||
|
|
||||||
|
// 解析Excel
|
||||||
|
List<SchoolDTO> schools = SchoolImporter.importFromXls(tempFile.getAbsolutePath());
|
||||||
|
|
||||||
|
// 导入数据库
|
||||||
|
int count = schoolService.batchImport(schools);
|
||||||
|
|
||||||
|
// 删除临时文件
|
||||||
|
tempFile.delete();
|
||||||
|
|
||||||
|
return Result.success(count);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return Result.error("导入失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从项目docs目录导入学校数据
|
||||||
|
*/
|
||||||
|
@ApiOperation("从项目docs目录导入学校数据")
|
||||||
|
@PostMapping("/import-from-docs")
|
||||||
|
public Result<Integer> importFromDocs() {
|
||||||
|
try {
|
||||||
|
String filePath = projectRoot + "/docs/school.xls";
|
||||||
|
File file = new File(filePath);
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
return Result.error("文件不存在: " + filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析Excel
|
||||||
|
List<SchoolDTO> schools = SchoolImporter.importFromXls(filePath);
|
||||||
|
|
||||||
|
// 导入数据库
|
||||||
|
int count = schoolService.batchImport(schools);
|
||||||
|
|
||||||
|
return Result.success(count);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Result.error("导入失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.bycrm.controller;
|
||||||
|
|
||||||
|
import com.bycrm.common.Result;
|
||||||
|
import com.bycrm.entity.SystemConfig;
|
||||||
|
import com.bycrm.entity.User;
|
||||||
|
import com.bycrm.mapper.UserMapper;
|
||||||
|
import com.bycrm.service.SystemConfigService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置控制器
|
||||||
|
*/
|
||||||
|
@Api(tags = "系统配置管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/config")
|
||||||
|
public class SystemConfigController {
|
||||||
|
|
||||||
|
private final SystemConfigService systemConfigService;
|
||||||
|
private final UserMapper userMapper;
|
||||||
|
|
||||||
|
public SystemConfigController(SystemConfigService systemConfigService,
|
||||||
|
UserMapper userMapper) {
|
||||||
|
this.systemConfigService = systemConfigService;
|
||||||
|
this.userMapper = userMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有配置(仅管理员)
|
||||||
|
*/
|
||||||
|
@ApiOperation("获取所有系统配置")
|
||||||
|
@GetMapping
|
||||||
|
public Result<List<SystemConfig>> getAllConfigs(HttpServletRequest request) {
|
||||||
|
// 权限校验:只有管理员可以查看
|
||||||
|
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||||
|
User currentUser = userMapper.selectById(currentUserId);
|
||||||
|
if (currentUser == null || currentUser.getRole() != 0) {
|
||||||
|
return Result.error("无权限访问");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SystemConfig> configs = systemConfigService.getAllConfigs();
|
||||||
|
return Result.success(configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置值(所有用户可访问)
|
||||||
|
*/
|
||||||
|
@ApiOperation("获取指定配置值")
|
||||||
|
@GetMapping("/value/{configKey}")
|
||||||
|
public Result<String> getConfigValue(@PathVariable String configKey) {
|
||||||
|
// 只允许访问特定配置,防止敏感信息泄露
|
||||||
|
if (!configKey.startsWith("report.")) {
|
||||||
|
return Result.error("无权访问该配置");
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = systemConfigService.getConfigValue(configKey);
|
||||||
|
return Result.success(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置(仅管理员)
|
||||||
|
*/
|
||||||
|
@ApiOperation("更新系统配置")
|
||||||
|
@PutMapping
|
||||||
|
public Result<Void> updateConfig(@RequestBody Map<String, String> params,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
// 权限校验
|
||||||
|
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||||
|
User currentUser = userMapper.selectById(currentUserId);
|
||||||
|
if (currentUser == null || currentUser.getRole() != 0) {
|
||||||
|
return Result.error("只有管理员才能修改配置");
|
||||||
|
}
|
||||||
|
|
||||||
|
String configKey = params.get("configKey");
|
||||||
|
String configValue = params.get("configValue");
|
||||||
|
|
||||||
|
if (configKey == null || configValue == null) {
|
||||||
|
return Result.error("参数不完整");
|
||||||
|
}
|
||||||
|
|
||||||
|
systemConfigService.updateConfig(configKey, configValue);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新配置(仅管理员)
|
||||||
|
*/
|
||||||
|
@ApiOperation("批量更新系统配置")
|
||||||
|
@PutMapping("/batch")
|
||||||
|
public Result<Void> batchUpdateConfigs(@RequestBody Map<String, String> configs,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
// 权限校验
|
||||||
|
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||||
|
User currentUser = userMapper.selectById(currentUserId);
|
||||||
|
if (currentUser == null || currentUser.getRole() != 0) {
|
||||||
|
return Result.error("只有管理员才能修改配置");
|
||||||
|
}
|
||||||
|
|
||||||
|
systemConfigService.batchUpdateConfigs(configs);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
backend/src/main/java/com/bycrm/dto/ChangePasswordDTO.java
Normal file
29
backend/src/main/java/com/bycrm/dto/ChangePasswordDTO.java
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.bycrm.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ChangePasswordDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原密码
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "原密码不能为空")
|
||||||
|
private String oldPassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新密码
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "新密码不能为空")
|
||||||
|
@Size(min = 6, max = 20, message = "新密码长度必须在6-20位之间")
|
||||||
|
private String newPassword;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.bycrm.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页统计DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DashboardStatisticsDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户总数
|
||||||
|
*/
|
||||||
|
private Long customerCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报备总数
|
||||||
|
*/
|
||||||
|
private Long reportCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待审核数量
|
||||||
|
*/
|
||||||
|
private Long pendingCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经销商总数(仅管理员可见)
|
||||||
|
*/
|
||||||
|
private Long dealerCount;
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ public class DealerDTO implements Serializable {
|
||||||
/**
|
/**
|
||||||
* 经销商编码
|
* 经销商编码
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "经销商编码不能为空")
|
@NotBlank(message = "经销商账号不能为空")
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,4 +48,9 @@ public class DealerDTO implements Serializable {
|
||||||
*/
|
*/
|
||||||
@NotNull(message = "状态不能为空")
|
@NotNull(message = "状态不能为空")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始密码(新增用户时可设置)
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
}
|
}
|
||||||
|
|
@ -22,6 +22,11 @@ public class PageQuery implements Serializable {
|
||||||
*/
|
*/
|
||||||
private Long size = 10L;
|
private Long size = 10L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 偏移量(自动计算,不需要手动设置)
|
||||||
|
*/
|
||||||
|
private Long offset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 排序字段
|
* 排序字段
|
||||||
*/
|
*/
|
||||||
28
backend/src/main/java/com/bycrm/dto/ResetPasswordDTO.java
Normal file
28
backend/src/main/java/com/bycrm/dto/ResetPasswordDTO.java
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.bycrm.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置密码DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ResetPasswordDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新密码
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "新密码不能为空")
|
||||||
|
@Size(min = 6, max = 20, message = "新密码长度必须在6-20位之间")
|
||||||
|
private String newPassword;
|
||||||
|
}
|
||||||
37
backend/src/main/java/com/bycrm/dto/SchoolDTO.java
Normal file
37
backend/src/main/java/com/bycrm/dto/SchoolDTO.java
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.bycrm.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SchoolDTO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校标识码
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "学校标识码不能为空")
|
||||||
|
private String schoolCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校名称
|
||||||
|
*/
|
||||||
|
@NotBlank(message = "学校名称不能为空")
|
||||||
|
private String schoolName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所在地
|
||||||
|
*/
|
||||||
|
private String location;
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,11 @@ public class Dealer implements Serializable {
|
||||||
*/
|
*/
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的用户ID(用于重置密码等操作)
|
||||||
|
*/
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,16 +46,16 @@ public class Report implements Serializable {
|
||||||
private String rejectReason;
|
private String rejectReason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保护期开始时间
|
* 保护期开始日期
|
||||||
*/
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDateTime protectStartDate;
|
private LocalDate protectStartDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保护期结束时间
|
* 保护期结束日期
|
||||||
*/
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDateTime protectEndDate;
|
private LocalDate protectEndDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
48
backend/src/main/java/com/bycrm/entity/School.java
Normal file
48
backend/src/main/java/com/bycrm/entity/School.java
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.bycrm.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class School implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校标识码
|
||||||
|
*/
|
||||||
|
private String schoolCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校名称
|
||||||
|
*/
|
||||||
|
private String schoolName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所在地
|
||||||
|
*/
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
63
backend/src/main/java/com/bycrm/entity/SystemConfig.java
Normal file
63
backend/src/main/java/com/bycrm/entity/SystemConfig.java
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.bycrm.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SystemConfig implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置键
|
||||||
|
*/
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置值
|
||||||
|
*/
|
||||||
|
private String configValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置名称
|
||||||
|
*/
|
||||||
|
private String configLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据类型:string/integer/boolean
|
||||||
|
*/
|
||||||
|
private String configType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可编辑:0-否 1-是
|
||||||
|
*/
|
||||||
|
private Integer isEditable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.bycrm.mapper;
|
||||||
|
|
||||||
import com.bycrm.entity.Customer;
|
import com.bycrm.entity.Customer;
|
||||||
import com.bycrm.dto.PageQuery;
|
import com.bycrm.dto.PageQuery;
|
||||||
|
import com.bycrm.vo.CustomerVO;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
|
@ -29,6 +30,12 @@ public interface CustomerMapper {
|
||||||
List<Customer> selectPage(@Param("query") PageQuery query, @Param("name") String name,
|
List<Customer> selectPage(@Param("query") PageQuery query, @Param("name") String name,
|
||||||
@Param("industry") String industry, @Param("status") Integer status);
|
@Param("industry") String industry, @Param("status") Integer status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询客户(带经销商信息)
|
||||||
|
*/
|
||||||
|
List<CustomerVO> selectPageWithDealer(@Param("query") PageQuery query, @Param("name") String name,
|
||||||
|
@Param("industry") String industry, @Param("status") Integer status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询客户总数
|
* 查询客户总数
|
||||||
*/
|
*/
|
||||||
|
|
@ -41,4 +41,9 @@ public interface DealerMapper {
|
||||||
* 删除经销商
|
* 删除经销商
|
||||||
*/
|
*/
|
||||||
int deleteById(@Param("id") Long id);
|
int deleteById(@Param("id") Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计经销商总数
|
||||||
|
*/
|
||||||
|
Long count();
|
||||||
}
|
}
|
||||||
|
|
@ -60,4 +60,14 @@ public interface ReportMapper {
|
||||||
* 批量更新报备状态为已失效
|
* 批量更新报备状态为已失效
|
||||||
*/
|
*/
|
||||||
int batchUpdateExpired(@Param("ids") List<Long> ids);
|
int batchUpdateExpired(@Param("ids") List<Long> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计报备总数(根据经销商ID过滤)
|
||||||
|
*/
|
||||||
|
Long countByDealerId(@Param("dealerId") Long dealerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计待审核报备数量(根据经销商ID过滤)
|
||||||
|
*/
|
||||||
|
Long countPendingByDealerId(@Param("dealerId") Long dealerId);
|
||||||
}
|
}
|
||||||
49
backend/src/main/java/com/bycrm/mapper/SchoolMapper.java
Normal file
49
backend/src/main/java/com/bycrm/mapper/SchoolMapper.java
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.bycrm.mapper;
|
||||||
|
|
||||||
|
import com.bycrm.entity.School;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface SchoolMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入学校
|
||||||
|
*/
|
||||||
|
int insert(School school);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入学校
|
||||||
|
*/
|
||||||
|
int batchInsert(@Param("list") List<School> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据学校名称模糊查询
|
||||||
|
*/
|
||||||
|
List<School> searchByName(@Param("keyword") String keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据学校标识码查询
|
||||||
|
*/
|
||||||
|
School findByCode(@Param("schoolCode") String schoolCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有学校
|
||||||
|
*/
|
||||||
|
List<School> findAll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询学校
|
||||||
|
*/
|
||||||
|
List<School> findByPage(@Param("offset") int offset, @Param("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计学校数量
|
||||||
|
*/
|
||||||
|
int count();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.bycrm.mapper;
|
||||||
|
|
||||||
|
import com.bycrm.entity.SystemConfig;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface SystemConfigMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有配置
|
||||||
|
*/
|
||||||
|
List<SystemConfig> selectAll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据配置键查询
|
||||||
|
*/
|
||||||
|
SystemConfig selectByKey(@Param("configKey") String configKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据配置键查询配置值
|
||||||
|
*/
|
||||||
|
String selectValueByKey(@Param("configKey") String configKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*/
|
||||||
|
int update(SystemConfig config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新配置
|
||||||
|
*/
|
||||||
|
int batchUpdate(@Param("configs") List<SystemConfig> configs);
|
||||||
|
}
|
||||||
|
|
@ -41,4 +41,14 @@ public interface ReportService {
|
||||||
* 处理过期报备(定时任务调用)
|
* 处理过期报备(定时任务调用)
|
||||||
*/
|
*/
|
||||||
void handleExpiredReports();
|
void handleExpiredReports();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计报备总数(根据经销商ID过滤)
|
||||||
|
*/
|
||||||
|
Long countByDealerId(Long dealerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计待审核报备数量(根据经销商ID过滤)
|
||||||
|
*/
|
||||||
|
Long countPendingByDealerId(Long dealerId);
|
||||||
}
|
}
|
||||||
32
backend/src/main/java/com/bycrm/service/SchoolService.java
Normal file
32
backend/src/main/java/com/bycrm/service/SchoolService.java
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.bycrm.service;
|
||||||
|
|
||||||
|
import com.bycrm.dto.SchoolDTO;
|
||||||
|
import com.bycrm.vo.SchoolVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校Service
|
||||||
|
*/
|
||||||
|
public interface SchoolService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据名称搜索学校(用于自动完成)
|
||||||
|
*/
|
||||||
|
List<SchoolVO> searchByName(String keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建学校
|
||||||
|
*/
|
||||||
|
Long create(SchoolDTO dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量导入学校
|
||||||
|
*/
|
||||||
|
int batchImport(List<SchoolDTO> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有学校
|
||||||
|
*/
|
||||||
|
List<SchoolVO> findAll();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package com.bycrm.service;
|
||||||
|
|
||||||
|
import com.bycrm.entity.SystemConfig;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置服务接口
|
||||||
|
*/
|
||||||
|
public interface SystemConfigService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有配置
|
||||||
|
*/
|
||||||
|
List<SystemConfig> getAllConfigs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据配置键获取配置值
|
||||||
|
*/
|
||||||
|
String getConfigValue(String configKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据配置键获取整数配置值
|
||||||
|
*/
|
||||||
|
Integer getIntegerConfigValue(String configKey, Integer defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据配置键获取布尔配置值
|
||||||
|
*/
|
||||||
|
Boolean getBooleanConfigValue(String configKey, Boolean defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有配置为Map
|
||||||
|
*/
|
||||||
|
Map<String, String> getConfigMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*/
|
||||||
|
void updateConfig(String configKey, String configValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新配置
|
||||||
|
*/
|
||||||
|
void batchUpdateConfigs(Map<String, String> configMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化配置(从数据库加载,如果不存在则使用默认值)
|
||||||
|
*/
|
||||||
|
void initConfigs();
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.bycrm.service;
|
package com.bycrm.service;
|
||||||
|
|
||||||
|
import com.bycrm.dto.ChangePasswordDTO;
|
||||||
import com.bycrm.dto.LoginDTO;
|
import com.bycrm.dto.LoginDTO;
|
||||||
|
import com.bycrm.dto.ResetPasswordDTO;
|
||||||
import com.bycrm.entity.User;
|
import com.bycrm.entity.User;
|
||||||
import com.bycrm.vo.UserInfoVO;
|
import com.bycrm.vo.UserInfoVO;
|
||||||
|
|
||||||
|
|
@ -28,4 +30,14 @@ public interface UserService {
|
||||||
* 获取当前登录用户信息
|
* 获取当前登录用户信息
|
||||||
*/
|
*/
|
||||||
UserInfoVO getCurrentUser(String token);
|
UserInfoVO getCurrentUser(String token);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码
|
||||||
|
*/
|
||||||
|
void changePassword(Long userId, ChangePasswordDTO dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置密码(管理员功能)
|
||||||
|
*/
|
||||||
|
void resetPassword(ResetPasswordDTO dto);
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
package com.bycrm.service.impl;
|
package com.bycrm.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import com.bycrm.common.Constants;
|
import com.bycrm.common.Constants;
|
||||||
import com.bycrm.common.PageResult;
|
import com.bycrm.common.PageResult;
|
||||||
import com.bycrm.dto.CustomerDTO;
|
import com.bycrm.dto.CustomerDTO;
|
||||||
import com.bycrm.dto.PageQuery;
|
import com.bycrm.dto.PageQuery;
|
||||||
import com.bycrm.entity.Customer;
|
import com.bycrm.entity.Customer;
|
||||||
import com.bycrm.entity.User;
|
|
||||||
import com.bycrm.exception.BusinessException;
|
import com.bycrm.exception.BusinessException;
|
||||||
import com.bycrm.mapper.CustomerMapper;
|
import com.bycrm.mapper.CustomerMapper;
|
||||||
import com.bycrm.mapper.UserMapper;
|
import com.bycrm.mapper.UserMapper;
|
||||||
|
|
@ -17,7 +15,6 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -40,15 +37,11 @@ public class CustomerServiceImpl implements CustomerService {
|
||||||
// 计算偏移量
|
// 计算偏移量
|
||||||
query.setOffset((query.getCurrent() - 1) * query.getSize());
|
query.setOffset((query.getCurrent() - 1) * query.getSize());
|
||||||
|
|
||||||
List<Customer> customers = customerMapper.selectPage(query, name, industry, status);
|
// 使用新的查询方法,直接返回 CustomerVO(包含经销商信息)
|
||||||
|
List<CustomerVO> customerVOs = customerMapper.selectPageWithDealer(query, name, industry, status);
|
||||||
Long total = customerMapper.countPage(name, industry, status);
|
Long total = customerMapper.countPage(name, industry, status);
|
||||||
|
|
||||||
List<CustomerVO> voList = customers.stream().map(customer -> {
|
return PageResult.of(total, customerVOs, query.getCurrent(), query.getSize());
|
||||||
CustomerVO vo = convertToVO(customer);
|
|
||||||
return vo;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
|
|
||||||
return PageResult.of(total, voList, query.getCurrent(), query.getSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
package com.bycrm.service.impl;
|
package com.bycrm.service.impl;
|
||||||
|
|
||||||
|
import com.bycrm.common.Constants;
|
||||||
import com.bycrm.dto.DealerDTO;
|
import com.bycrm.dto.DealerDTO;
|
||||||
import com.bycrm.entity.Dealer;
|
import com.bycrm.entity.Dealer;
|
||||||
|
import com.bycrm.entity.User;
|
||||||
import com.bycrm.exception.BusinessException;
|
import com.bycrm.exception.BusinessException;
|
||||||
import com.bycrm.mapper.DealerMapper;
|
import com.bycrm.mapper.DealerMapper;
|
||||||
|
import com.bycrm.mapper.UserMapper;
|
||||||
import com.bycrm.service.DealerService;
|
import com.bycrm.service.DealerService;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
|
@ -19,9 +23,12 @@ import java.util.List;
|
||||||
public class DealerServiceImpl implements DealerService {
|
public class DealerServiceImpl implements DealerService {
|
||||||
|
|
||||||
private final DealerMapper dealerMapper;
|
private final DealerMapper dealerMapper;
|
||||||
|
private final UserMapper userMapper;
|
||||||
|
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||||
|
|
||||||
public DealerServiceImpl(DealerMapper dealerMapper) {
|
public DealerServiceImpl(DealerMapper dealerMapper, UserMapper userMapper) {
|
||||||
this.dealerMapper = dealerMapper;
|
this.dealerMapper = dealerMapper;
|
||||||
|
this.userMapper = userMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -44,7 +51,7 @@ public class DealerServiceImpl implements DealerService {
|
||||||
// 检查编码是否重复
|
// 检查编码是否重复
|
||||||
Dealer existingDealer = dealerMapper.selectByCode(dealerDTO.getCode());
|
Dealer existingDealer = dealerMapper.selectByCode(dealerDTO.getCode());
|
||||||
if (existingDealer != null) {
|
if (existingDealer != null) {
|
||||||
throw new BusinessException("经销商编码已存在");
|
throw new BusinessException("经销商账号已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
Dealer dealer = new Dealer();
|
Dealer dealer = new Dealer();
|
||||||
|
|
@ -53,6 +60,25 @@ public class DealerServiceImpl implements DealerService {
|
||||||
dealer.setUpdatedAt(LocalDateTime.now());
|
dealer.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
dealerMapper.insert(dealer);
|
dealerMapper.insert(dealer);
|
||||||
|
|
||||||
|
// 创建对应的用户账号
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(dealerDTO.getCode()); // 用户名使用经销商编码
|
||||||
|
user.setRealName(dealerDTO.getContactPerson());
|
||||||
|
user.setDealerId(dealer.getId());
|
||||||
|
user.setRole(Constants.USER_ROLE_DEALER); // 经销商用户角色
|
||||||
|
user.setStatus(dealerDTO.getStatus());
|
||||||
|
|
||||||
|
// 设置密码:如果提供了密码则使用提供的密码,否则使用默认密码
|
||||||
|
String password = dealerDTO.getPassword();
|
||||||
|
if (password == null || password.trim().isEmpty()) {
|
||||||
|
password = "123456"; // 默认密码
|
||||||
|
}
|
||||||
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
|
user.setCreatedAt(LocalDateTime.now());
|
||||||
|
user.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
userMapper.insert(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -67,7 +93,7 @@ public class DealerServiceImpl implements DealerService {
|
||||||
if (!existingDealer.getCode().equals(dealerDTO.getCode())) {
|
if (!existingDealer.getCode().equals(dealerDTO.getCode())) {
|
||||||
Dealer codeDealer = dealerMapper.selectByCode(dealerDTO.getCode());
|
Dealer codeDealer = dealerMapper.selectByCode(dealerDTO.getCode());
|
||||||
if (codeDealer != null && !codeDealer.getId().equals(id)) {
|
if (codeDealer != null && !codeDealer.getId().equals(id)) {
|
||||||
throw new BusinessException("经销商编码已存在");
|
throw new BusinessException("经销商账号已存在");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,9 +13,9 @@ import com.bycrm.mapper.CustomerMapper;
|
||||||
import com.bycrm.mapper.ReportMapper;
|
import com.bycrm.mapper.ReportMapper;
|
||||||
import com.bycrm.mapper.UserMapper;
|
import com.bycrm.mapper.UserMapper;
|
||||||
import com.bycrm.service.ReportService;
|
import com.bycrm.service.ReportService;
|
||||||
|
import com.bycrm.service.SystemConfigService;
|
||||||
import com.bycrm.vo.ReportVO;
|
import com.bycrm.vo.ReportVO;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
|
@ -34,17 +34,16 @@ public class ReportServiceImpl implements ReportService {
|
||||||
private final ReportMapper reportMapper;
|
private final ReportMapper reportMapper;
|
||||||
private final CustomerMapper customerMapper;
|
private final CustomerMapper customerMapper;
|
||||||
private final UserMapper userMapper;
|
private final UserMapper userMapper;
|
||||||
|
private final SystemConfigService systemConfigService;
|
||||||
|
|
||||||
@Value("${crm.report.ttl-days:90}")
|
public ReportServiceImpl(ReportMapper reportMapper,
|
||||||
private Integer protectDays;
|
CustomerMapper customerMapper,
|
||||||
|
UserMapper userMapper,
|
||||||
@Value("${crm.report.allow-overlap:false}")
|
SystemConfigService systemConfigService) {
|
||||||
private Boolean allowOverlap;
|
|
||||||
|
|
||||||
public ReportServiceImpl(ReportMapper reportMapper, CustomerMapper customerMapper, UserMapper userMapper) {
|
|
||||||
this.reportMapper = reportMapper;
|
this.reportMapper = reportMapper;
|
||||||
this.customerMapper = customerMapper;
|
this.customerMapper = customerMapper;
|
||||||
this.userMapper = userMapper;
|
this.userMapper = userMapper;
|
||||||
|
this.systemConfigService = systemConfigService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -95,6 +94,7 @@ public class ReportServiceImpl implements ReportService {
|
||||||
|
|
||||||
// 防撞单校验:检查该客户是否已存在有效报备
|
// 防撞单校验:检查该客户是否已存在有效报备
|
||||||
Report existingReport = reportMapper.selectValidByCustomerId(reportDTO.getCustomerId());
|
Report existingReport = reportMapper.selectValidByCustomerId(reportDTO.getCustomerId());
|
||||||
|
Boolean allowOverlap = systemConfigService.getBooleanConfigValue("report.allow.overlap", false);
|
||||||
if (existingReport != null && !allowOverlap) {
|
if (existingReport != null && !allowOverlap) {
|
||||||
throw new BusinessException("该客户已被其他经销商报备,无法重复报备");
|
throw new BusinessException("该客户已被其他经销商报备,无法重复报备");
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +134,10 @@ public class ReportServiceImpl implements ReportService {
|
||||||
if (auditDTO.getApproved()) {
|
if (auditDTO.getApproved()) {
|
||||||
// 审核通过
|
// 审核通过
|
||||||
report.setStatus(Constants.REPORT_STATUS_APPROVED);
|
report.setStatus(Constants.REPORT_STATUS_APPROVED);
|
||||||
report.setProtectStartDate(now);
|
LocalDate today = LocalDate.now();
|
||||||
report.setProtectEndDate(now.plusDays(protectDays));
|
report.setProtectStartDate(today);
|
||||||
|
Integer protectDays = systemConfigService.getIntegerConfigValue("report.protect.days", Constants.DEFAULT_PROTECT_DAYS);
|
||||||
|
report.setProtectEndDate(today.plusDays(protectDays));
|
||||||
|
|
||||||
// 更新客户状态为保护中
|
// 更新客户状态为保护中
|
||||||
Customer customer = customerMapper.selectById(report.getCustomerId());
|
Customer customer = customerMapper.selectById(report.getCustomerId());
|
||||||
|
|
@ -177,7 +179,7 @@ public class ReportServiceImpl implements ReportService {
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void handleExpiredReports() {
|
public void handleExpiredReports() {
|
||||||
// 查询所有保护期已到的报备
|
// 查询所有保护期已到的报备
|
||||||
String endDate = LocalDateTime.now().toString();
|
String endDate = LocalDate.now().toString();
|
||||||
List<Report> expiringReports = reportMapper.selectExpiringReports(endDate);
|
List<Report> expiringReports = reportMapper.selectExpiringReports(endDate);
|
||||||
|
|
||||||
if (expiringReports.isEmpty()) {
|
if (expiringReports.isEmpty()) {
|
||||||
|
|
@ -198,6 +200,16 @@ public class ReportServiceImpl implements ReportService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long countByDealerId(Long dealerId) {
|
||||||
|
return reportMapper.countByDealerId(dealerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long countPendingByDealerId(Long dealerId) {
|
||||||
|
return reportMapper.countPendingByDealerId(dealerId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换为 VO
|
* 转换为 VO
|
||||||
*/
|
*/
|
||||||
|
|
@ -214,7 +226,7 @@ public class ReportServiceImpl implements ReportService {
|
||||||
vo.setStatusDesc("已通过");
|
vo.setStatusDesc("已通过");
|
||||||
// 计算剩余保护天数
|
// 计算剩余保护天数
|
||||||
if (report.getProtectEndDate() != null) {
|
if (report.getProtectEndDate() != null) {
|
||||||
long days = ChronoUnit.DAYS.between(LocalDateTime.now(), report.getProtectEndDate());
|
long days = ChronoUnit.DAYS.between(LocalDate.now(), report.getProtectEndDate());
|
||||||
vo.setRemainDays((int) Math.max(0, days));
|
vo.setRemainDays((int) Math.max(0, days));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.bycrm.service.impl;
|
||||||
|
|
||||||
|
import com.bycrm.dto.SchoolDTO;
|
||||||
|
import com.bycrm.entity.School;
|
||||||
|
import com.bycrm.exception.BusinessException;
|
||||||
|
import com.bycrm.mapper.SchoolMapper;
|
||||||
|
import com.bycrm.service.SchoolService;
|
||||||
|
import com.bycrm.vo.SchoolVO;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
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.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校服务实现
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SchoolServiceImpl implements SchoolService {
|
||||||
|
|
||||||
|
private final SchoolMapper schoolMapper;
|
||||||
|
|
||||||
|
public SchoolServiceImpl(SchoolMapper schoolMapper) {
|
||||||
|
this.schoolMapper = schoolMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchoolVO> searchByName(String keyword) {
|
||||||
|
if (keyword == null || keyword.trim().isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<School> schools = schoolMapper.searchByName(keyword.trim());
|
||||||
|
return schools.stream()
|
||||||
|
.map(this::convertToVO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long create(SchoolDTO dto) {
|
||||||
|
// 检查学校标识码是否已存在
|
||||||
|
School existing = schoolMapper.findByCode(dto.getSchoolCode());
|
||||||
|
if (existing != null) {
|
||||||
|
throw new BusinessException("学校标识码已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
School school = new School();
|
||||||
|
BeanUtils.copyProperties(dto, school);
|
||||||
|
school.setCreatedAt(LocalDateTime.now());
|
||||||
|
school.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
schoolMapper.insert(school);
|
||||||
|
return school.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public int batchImport(List<SchoolDTO> list) {
|
||||||
|
if (list == null || list.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
List<School> schools = list.stream()
|
||||||
|
.map(dto -> {
|
||||||
|
School school = new School();
|
||||||
|
BeanUtils.copyProperties(dto, school);
|
||||||
|
school.setCreatedAt(now);
|
||||||
|
school.setUpdatedAt(now);
|
||||||
|
return school;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return schoolMapper.batchInsert(schools);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SchoolVO> findAll() {
|
||||||
|
List<School> schools = schoolMapper.findAll();
|
||||||
|
return schools.stream()
|
||||||
|
.map(this::convertToVO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为 VO
|
||||||
|
*/
|
||||||
|
private SchoolVO convertToVO(School school) {
|
||||||
|
SchoolVO vo = new SchoolVO();
|
||||||
|
BeanUtils.copyProperties(school, vo);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
package com.bycrm.service.impl;
|
||||||
|
|
||||||
|
import com.bycrm.common.Constants;
|
||||||
|
import com.bycrm.entity.SystemConfig;
|
||||||
|
import com.bycrm.exception.BusinessException;
|
||||||
|
import com.bycrm.mapper.SystemConfigMapper;
|
||||||
|
import com.bycrm.service.SystemConfigService;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置服务实现
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SystemConfigServiceImpl implements SystemConfigService {
|
||||||
|
|
||||||
|
private final SystemConfigMapper systemConfigMapper;
|
||||||
|
|
||||||
|
// 从配置文件读取默认值
|
||||||
|
@Value("${crm.report.ttl-days:90}")
|
||||||
|
private Integer defaultProtectDays;
|
||||||
|
|
||||||
|
@Value("${crm.report.allow-overlap:false}")
|
||||||
|
private Boolean defaultAllowOverlap;
|
||||||
|
|
||||||
|
// 本地缓存,避免频繁查询数据库
|
||||||
|
private Map<String, String> configCache = new HashMap<>();
|
||||||
|
|
||||||
|
public SystemConfigServiceImpl(SystemConfigMapper systemConfigMapper) {
|
||||||
|
this.systemConfigMapper = systemConfigMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
initConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initConfigs() {
|
||||||
|
List<SystemConfig> configs = systemConfigMapper.selectAll();
|
||||||
|
|
||||||
|
// 如果数据库为空,插入默认配置
|
||||||
|
if (configs.isEmpty()) {
|
||||||
|
insertDefaultConfigs();
|
||||||
|
configs = systemConfigMapper.selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建缓存
|
||||||
|
configCache = configs.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
SystemConfig::getConfigKey,
|
||||||
|
SystemConfig::getConfigValue
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入默认配置
|
||||||
|
*/
|
||||||
|
private void insertDefaultConfigs() {
|
||||||
|
SystemConfig protectDaysConfig = new SystemConfig();
|
||||||
|
protectDaysConfig.setConfigKey("report.protect.days");
|
||||||
|
protectDaysConfig.setConfigValue(String.valueOf(defaultProtectDays));
|
||||||
|
protectDaysConfig.setConfigLabel("报备保护期天数");
|
||||||
|
protectDaysConfig.setConfigType("integer");
|
||||||
|
protectDaysConfig.setDescription("报备审核通过后客户的保护天数");
|
||||||
|
protectDaysConfig.setIsEditable(1);
|
||||||
|
systemConfigMapper.update(protectDaysConfig);
|
||||||
|
|
||||||
|
// 先插入再更新,因为update方法可能失败,我们需要insert
|
||||||
|
// 这里为了简单,我们直接使用SQL插入
|
||||||
|
// 实际应该添加insert方法到mapper
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SystemConfig> getAllConfigs() {
|
||||||
|
return systemConfigMapper.selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigValue(String configKey) {
|
||||||
|
// 优先从缓存读取
|
||||||
|
String value = configCache.get(configKey);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存未命中,查询数据库
|
||||||
|
value = systemConfigMapper.selectValueByKey(configKey);
|
||||||
|
if (value != null) {
|
||||||
|
configCache.put(configKey, value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getIntegerConfigValue(String configKey, Integer defaultValue) {
|
||||||
|
String value = getConfigValue(configKey);
|
||||||
|
if (value == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean getBooleanConfigValue(String configKey, Boolean defaultValue) {
|
||||||
|
String value = getConfigValue(configKey);
|
||||||
|
if (value == null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return Boolean.parseBoolean(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getConfigMap() {
|
||||||
|
return new HashMap<>(configCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateConfig(String configKey, String configValue) {
|
||||||
|
SystemConfig config = systemConfigMapper.selectByKey(configKey);
|
||||||
|
if (config == null) {
|
||||||
|
throw new BusinessException("配置项不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.getIsEditable() == 0) {
|
||||||
|
throw new BusinessException("该配置项不允许修改");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据类型校验
|
||||||
|
validateConfigType(config.getConfigType(), configValue);
|
||||||
|
|
||||||
|
config.setConfigValue(configValue);
|
||||||
|
systemConfigMapper.update(config);
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
configCache.put(configKey, configValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void batchUpdateConfigs(Map<String, String> configMap) {
|
||||||
|
configMap.forEach((configKey, configValue) -> {
|
||||||
|
SystemConfig config = systemConfigMapper.selectByKey(configKey);
|
||||||
|
if (config != null && config.getIsEditable() == 1) {
|
||||||
|
// 数据类型校验
|
||||||
|
validateConfigType(config.getConfigType(), configValue);
|
||||||
|
|
||||||
|
// 更新配置
|
||||||
|
config.setConfigValue(configValue);
|
||||||
|
systemConfigMapper.update(config);
|
||||||
|
|
||||||
|
// 更新缓存
|
||||||
|
configCache.put(configKey, configValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验配置类型
|
||||||
|
*/
|
||||||
|
private void validateConfigType(String configType, String configValue) {
|
||||||
|
switch (configType) {
|
||||||
|
case "integer":
|
||||||
|
try {
|
||||||
|
Integer.parseInt(configValue);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new BusinessException("配置值必须是整数");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "boolean":
|
||||||
|
if (!"true".equals(configValue) && !"false".equals(configValue)) {
|
||||||
|
throw new BusinessException("配置值必须是 true 或 false");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
// 字符串类型无需校验
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new BusinessException("未知的配置类型:" + configType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.bycrm.service.impl;
|
package com.bycrm.service.impl;
|
||||||
|
|
||||||
import com.bycrm.common.Constants;
|
import com.bycrm.common.Constants;
|
||||||
|
import com.bycrm.dto.ChangePasswordDTO;
|
||||||
|
import com.bycrm.dto.ResetPasswordDTO;
|
||||||
import com.bycrm.entity.User;
|
import com.bycrm.entity.User;
|
||||||
import com.bycrm.exception.BusinessException;
|
import com.bycrm.exception.BusinessException;
|
||||||
import com.bycrm.mapper.UserMapper;
|
import com.bycrm.mapper.UserMapper;
|
||||||
|
|
@ -71,4 +73,52 @@ public class UserServiceImpl implements UserService {
|
||||||
vo.setRoleDesc(user.getRole() == Constants.USER_ROLE_ADMIN ? "管理员" : "经销商用户");
|
vo.setRoleDesc(user.getRole() == Constants.USER_ROLE_ADMIN ? "管理员" : "经销商用户");
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changePassword(Long userId, ChangePasswordDTO dto) {
|
||||||
|
User user = userMapper.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
throw new BusinessException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证原密码
|
||||||
|
if (!passwordEncoder.matches(dto.getOldPassword(), user.getPassword())) {
|
||||||
|
throw new BusinessException("原密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新密码不能与原密码相同
|
||||||
|
if (dto.getOldPassword().equals(dto.getNewPassword())) {
|
||||||
|
throw new BusinessException("新密码不能与原密码相同");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密新密码并更新
|
||||||
|
String encodedPassword = passwordEncoder.encode(dto.getNewPassword());
|
||||||
|
user.setPassword(encodedPassword);
|
||||||
|
userMapper.update(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetPassword(ResetPasswordDTO dto) {
|
||||||
|
User user = userMapper.selectById(dto.getUserId());
|
||||||
|
if (user == null) {
|
||||||
|
throw new BusinessException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 管理员不能重置自己的密码(应使用修改密码功能)
|
||||||
|
if (user.getRole() == Constants.USER_ROLE_ADMIN) {
|
||||||
|
throw new BusinessException("不能重置管理员密码");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密新密码并更新
|
||||||
|
String encodedPassword = passwordEncoder.encode(dto.getNewPassword());
|
||||||
|
user.setPassword(encodedPassword);
|
||||||
|
userMapper.update(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||||
|
String rawPassword = "59cce3254e6d76ec6bad2d84ae56b5da3e118b2f22d0d2b2d780356cbe3c0881";
|
||||||
|
String encodedPassword = passwordEncoder.encode(rawPassword);
|
||||||
|
System.out.println("Encoded Password: " + encodedPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
117
backend/src/main/java/com/bycrm/util/CheckExcelStructure.java
Normal file
117
backend/src/main/java/com/bycrm/util/CheckExcelStructure.java
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.bycrm.util;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查Excel文件结构的工具类
|
||||||
|
* 用于调试和数据预览
|
||||||
|
*/
|
||||||
|
public class CheckExcelStructure {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String filePath = "docs/school.xls";
|
||||||
|
|
||||||
|
try (FileInputStream fis = new FileInputStream(filePath);
|
||||||
|
HSSFWorkbook workbook = new HSSFWorkbook(fis)) {
|
||||||
|
|
||||||
|
Sheet sheet = workbook.getSheetAt(0);
|
||||||
|
|
||||||
|
System.out.println("=== Excel 文件结构检查 ===");
|
||||||
|
System.out.println("文件路径: " + filePath);
|
||||||
|
System.out.println("工作表名称: " + sheet.getSheetName());
|
||||||
|
System.out.println("总行数: " + (sheet.getLastRowNum() + 1));
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
// 显示标题行
|
||||||
|
System.out.println("=== 标题行(第1行)===");
|
||||||
|
Row headerRow = sheet.getRow(0);
|
||||||
|
if (headerRow != null) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
Cell cell = headerRow.getCell(i);
|
||||||
|
String value = getCellValueAsString(cell);
|
||||||
|
System.out.printf(" 列%c: %s%n", 'A' + i, value != null ? value : "[空]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
// 显示前5行数据
|
||||||
|
System.out.println("=== 数据预览(前5行)===");
|
||||||
|
int previewRows = Math.min(6, sheet.getLastRowNum() + 1);
|
||||||
|
for (int i = 1; i < previewRows; i++) {
|
||||||
|
Row row = sheet.getRow(i);
|
||||||
|
if (row == null) {
|
||||||
|
System.out.println(" 第" + (i + 1) + "行: [空行]");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.printf(" 第%d行:%n", i + 1);
|
||||||
|
for (int j = 0; j < 4; j++) {
|
||||||
|
Cell cell = row.getCell(j);
|
||||||
|
String value = getCellValueAsString(cell);
|
||||||
|
System.out.printf(" 列%c: %s%n", 'A' + j, value != null ? value : "[空]");
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析后的数据预览
|
||||||
|
System.out.println("=== 解析后的数据预览(前5条)===");
|
||||||
|
System.out.println("字段映射:");
|
||||||
|
System.out.println(" school_code <- 列A(学校标识码)");
|
||||||
|
System.out.println(" school_name <- 列B(学校名称)");
|
||||||
|
System.out.println(" location <- 列C(主管部门+所在地)");
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 1; i <= sheet.getLastRowNum() && count < 5; i++) {
|
||||||
|
Row row = sheet.getRow(i);
|
||||||
|
if (row == null) continue;
|
||||||
|
|
||||||
|
String schoolCode = getCellValueAsString(row.getCell(0));
|
||||||
|
String schoolName = getCellValueAsString(row.getCell(1));
|
||||||
|
String location = getCellValueAsString(row.getCell(2));
|
||||||
|
|
||||||
|
if (schoolCode != null && schoolName != null) {
|
||||||
|
System.out.printf(" %d. school_code=%s, school_name=%s%n", count + 1, schoolCode, schoolName);
|
||||||
|
System.out.printf(" location=%s%n", location != null ? location : "[空]");
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("读取文件失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCellValueAsString(Cell cell) {
|
||||||
|
if (cell == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (cell.getCellType()) {
|
||||||
|
case STRING:
|
||||||
|
return cell.getStringCellValue();
|
||||||
|
case NUMERIC:
|
||||||
|
double numericValue = cell.getNumericCellValue();
|
||||||
|
if (numericValue == (long) numericValue) {
|
||||||
|
return String.valueOf((long) numericValue);
|
||||||
|
} else {
|
||||||
|
return String.valueOf(numericValue);
|
||||||
|
}
|
||||||
|
case BOOLEAN:
|
||||||
|
return String.valueOf(cell.getBooleanCellValue());
|
||||||
|
case FORMULA:
|
||||||
|
return cell.getCellFormula();
|
||||||
|
case BLANK:
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
backend/src/main/java/com/bycrm/util/ImportSchools.java
Normal file
55
backend/src/main/java/com/bycrm/util/ImportSchools.java
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.bycrm.util;
|
||||||
|
|
||||||
|
import com.bycrm.dto.SchoolDTO;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校数据导入执行类
|
||||||
|
* 使用方法:运行 main 方法,传入 school.xls 文件路径
|
||||||
|
*/
|
||||||
|
public class ImportSchools {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 文件路径参数
|
||||||
|
String filePath;
|
||||||
|
if (args.length > 0) {
|
||||||
|
filePath = args[0];
|
||||||
|
} else {
|
||||||
|
// 默认路径:项目根目录下的 docs/school.xls
|
||||||
|
filePath = "docs/school.xls";
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = new File(filePath);
|
||||||
|
if (!file.exists()) {
|
||||||
|
System.err.println("文件不存在: " + file.getAbsolutePath());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
System.out.println("开始读取文件: " + file.getAbsolutePath());
|
||||||
|
List<SchoolDTO> schools = SchoolImporter.importFromXls(filePath);
|
||||||
|
System.out.println("成功读取 " + schools.size() + " 条学校数据");
|
||||||
|
|
||||||
|
// 打印前5条数据预览
|
||||||
|
System.out.println("\n数据预览(前5条):");
|
||||||
|
int previewCount = Math.min(5, schools.size());
|
||||||
|
for (int i = 0; i < previewCount; i++) {
|
||||||
|
SchoolDTO dto = schools.get(i);
|
||||||
|
System.out.printf("%d. [%s] %s - %s%n",
|
||||||
|
i + 1, dto.getSchoolCode(), dto.getSchoolName(), dto.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到文件供后续使用
|
||||||
|
System.out.println("\n数据已准备好,请通过 API 接口导入到数据库");
|
||||||
|
System.out.println("或者使用 SchoolService.batchImport() 方法批量导入");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("导入失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
backend/src/main/java/com/bycrm/util/PasswordTest.java
Normal file
28
backend/src/main/java/com/bycrm/util/PasswordTest.java
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.bycrm.util;
|
||||||
|
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码测试工具类 - 用于生成 BCrypt 哈希值
|
||||||
|
*/
|
||||||
|
public class PasswordTest {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||||
|
|
||||||
|
String rawPassword = "admin123";
|
||||||
|
String hash = encoder.encode(rawPassword);
|
||||||
|
|
||||||
|
System.out.println("原始密码: " + rawPassword);
|
||||||
|
System.out.println("BCrypt 哈希值: " + hash);
|
||||||
|
System.out.println("验证结果: " + encoder.matches(rawPassword, hash));
|
||||||
|
|
||||||
|
// 生成多个哈希值,验证 BCrypt 每次生成的哈希值不同
|
||||||
|
System.out.println("\n生成多个哈希值测试:");
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
String hash2 = encoder.encode(rawPassword);
|
||||||
|
System.out.println("哈希 " + (i + 1) + ": " + hash2);
|
||||||
|
System.out.println("验证: " + encoder.matches(rawPassword, hash2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
243
backend/src/main/java/com/bycrm/util/SchoolImporter.java
Normal file
243
backend/src/main/java/com/bycrm/util/SchoolImporter.java
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
package com.bycrm.util;
|
||||||
|
|
||||||
|
import com.bycrm.dto.SchoolDTO;
|
||||||
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.CellType;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校数据导入工具类
|
||||||
|
*/
|
||||||
|
public class SchoolImporter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 .xls 文件导入学校数据
|
||||||
|
*
|
||||||
|
* @param filePath 文件路径
|
||||||
|
* @return 学校数据列表
|
||||||
|
* @throws IOException 读取文件失败
|
||||||
|
*/
|
||||||
|
public static List<SchoolDTO> importFromXls(String filePath) throws IOException {
|
||||||
|
List<SchoolDTO> schools = new ArrayList<>();
|
||||||
|
|
||||||
|
try (FileInputStream fis = new FileInputStream(filePath);
|
||||||
|
HSSFWorkbook workbook = new HSSFWorkbook(fis)) {
|
||||||
|
|
||||||
|
Sheet sheet = workbook.getSheetAt(0); // 读取第一个工作表
|
||||||
|
|
||||||
|
// 跳过标题行,从第2行开始读取数据
|
||||||
|
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
|
||||||
|
Row row = sheet.getRow(i);
|
||||||
|
if (row == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SchoolDTO dto = parseRow(row);
|
||||||
|
if (dto != null) {
|
||||||
|
schools.add(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return schools;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析一行数据
|
||||||
|
*
|
||||||
|
* @param row 行对象
|
||||||
|
* @return 学校DTO,如果该行无效则返回null
|
||||||
|
*/
|
||||||
|
private static SchoolDTO parseRow(Row row) {
|
||||||
|
try {
|
||||||
|
// Excel列结构(用户已手动调整):
|
||||||
|
// A列=学校标识码,B列=学校名称,C列=主管部门+所在地
|
||||||
|
String schoolCode = getCellValueAsString(row.getCell(2));
|
||||||
|
String schoolName = getCellValueAsString(row.getCell(1));
|
||||||
|
String location = getCellValueAsString(row.getCell(3)) + getCellValueAsString(row.getCell(4)); // 直接读取已组合好的location
|
||||||
|
|
||||||
|
// 验证必填字段
|
||||||
|
if (schoolCode == null || schoolCode.trim().isEmpty() ||
|
||||||
|
schoolName == null || schoolName.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
SchoolDTO dto = new SchoolDTO();
|
||||||
|
dto.setSchoolCode(schoolCode.trim());
|
||||||
|
dto.setSchoolName(schoolName.trim());
|
||||||
|
dto.setLocation(location.trim());
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("解析行数据失败: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从主管部门中提取省份或城市名称(公开方法,供调试使用)
|
||||||
|
* 中央部委列表(无法提取省/市)
|
||||||
|
* 例如:
|
||||||
|
* - "北京市(92所)" -> "北京市"
|
||||||
|
* - "山西省教育厅" -> "山西省"
|
||||||
|
* - "教育部" -> null
|
||||||
|
* - "工业和信息化部" -> null
|
||||||
|
*
|
||||||
|
* @param competentDept 主管部门
|
||||||
|
* @return 省份或城市名称,如果无法提取则返回null
|
||||||
|
*/
|
||||||
|
public static String extractProvinceOrCity(String competentDept) {
|
||||||
|
if (competentDept == null || competentDept.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String dept = competentDept.trim();
|
||||||
|
|
||||||
|
// 中央部委列表,这些部门无法提取省/市信息
|
||||||
|
String[] centralMinistries = {
|
||||||
|
"教育部", "工业和信息化部", "公安部", "民政部", "司法部",
|
||||||
|
"财政部", "人力资源和社会保障部", "自然资源部", "生态环境部",
|
||||||
|
"住房和城乡建设部", "交通运输部", "水利部", "农业农村部",
|
||||||
|
"商务部", "文化和旅游部", "国家卫生健康委员会", "退役军人事务部",
|
||||||
|
"应急管理部", "中国人民银行", "审计署", "国家外国专家局",
|
||||||
|
"国家广播电视总局", "国家体育总局", "国家统计局", "国家国际发展合作署",
|
||||||
|
"国家医疗保障局", "国务院国有资产监督管理委员会", "海关总署",
|
||||||
|
"国家税务总局", "国家市场监督管理总局", "国家广播电视总局",
|
||||||
|
"国家新闻出版署", "国家版权局", "国家信访局", "国家粮食和物资储备局",
|
||||||
|
"国家林业和草原局", "国家烟草专卖局", "国家煤矿安全监察局",
|
||||||
|
"国家矿山安全监察局", "中国民用航空局", "国家邮政局",
|
||||||
|
"国家文物局", "国家中医药管理局", "国家外汇管理局",
|
||||||
|
"国家药品监督管理局", "国家知识产权局", "国家移民管理局",
|
||||||
|
"国家铁路局", "交通运输部", "应急管理部", "中国科学院",
|
||||||
|
"中国工程院", "国务院发展研究中心", "中国社科院"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查是否是中央部委
|
||||||
|
for (String ministry : centralMinistries) {
|
||||||
|
if (dept.contains(ministry)) {
|
||||||
|
return null; // 中央部委,无法提取省/市
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配模式1:括号内的数量格式,如"北京市(92所)"、"山西省(82所)"
|
||||||
|
// 这种格式优先级最高,因为最明确
|
||||||
|
int parenthesisIndex = dept.indexOf("(");
|
||||||
|
if (parenthesisIndex > 0) {
|
||||||
|
String beforeParenthesis = dept.substring(0, parenthesisIndex).trim();
|
||||||
|
// 检查是否以省、市、自治区结尾
|
||||||
|
if (beforeParenthesis.endsWith("省") || beforeParenthesis.endsWith("市") ||
|
||||||
|
beforeParenthesis.endsWith("自治区")) {
|
||||||
|
return beforeParenthesis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配模式2:省份模式 XX省
|
||||||
|
if (dept.contains("省")) {
|
||||||
|
int index = dept.indexOf("省");
|
||||||
|
// 确保省名不是"教育部"等
|
||||||
|
String province = dept.substring(0, index + 1);
|
||||||
|
// 过滤掉特殊情况,如"省教育厅"
|
||||||
|
if (province.length() <= 4 && !province.contains("部")) {
|
||||||
|
return province;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配模式3:自治区模式
|
||||||
|
if (dept.contains("自治区")) {
|
||||||
|
int index = dept.indexOf("自治区");
|
||||||
|
return dept.substring(0, index + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配模式4:直辖市模式
|
||||||
|
String[] municipalities = {"北京市", "上海市", "天津市", "重庆市"};
|
||||||
|
for (String city : municipalities) {
|
||||||
|
if (dept.contains(city)) {
|
||||||
|
return city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匹配模式5:其他城市模式 XX市
|
||||||
|
// 只提取简单的城市名,排除"XX市教育局"等
|
||||||
|
if (dept.contains("市")) {
|
||||||
|
int index = dept.indexOf("市");
|
||||||
|
// 确保是完整的地名(2-4个字符),且不包含"厅"、"局"等
|
||||||
|
if (index >= 2 && index <= 4) {
|
||||||
|
String city = dept.substring(0, index + 1);
|
||||||
|
if (!city.contains("厅") && !city.contains("局") && !city.contains("委")) {
|
||||||
|
return city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // 无法提取
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建完整的location字符串(公开方法,供调试使用)
|
||||||
|
*
|
||||||
|
* @param provinceOrCity 省份或城市名称(可能为null)
|
||||||
|
* @param location 所在地(可能为null)
|
||||||
|
* @return 完整的location字符串
|
||||||
|
*/
|
||||||
|
public static String buildLocation(String provinceOrCity, String location) {
|
||||||
|
if (provinceOrCity == null && location == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provinceOrCity == null) {
|
||||||
|
return location.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location == null || location.trim().isEmpty()) {
|
||||||
|
return provinceOrCity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组合:主管部门 + 所在地
|
||||||
|
return provinceOrCity + location.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单元格值作为字符串
|
||||||
|
*
|
||||||
|
* @param cell 单元格
|
||||||
|
* @return 字符串值
|
||||||
|
*/
|
||||||
|
private static String getCellValueAsString(Cell cell) {
|
||||||
|
if (cell == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CellType cellType = cell.getCellType();
|
||||||
|
switch (cellType) {
|
||||||
|
case STRING:
|
||||||
|
return cell.getStringCellValue();
|
||||||
|
case NUMERIC:
|
||||||
|
// 数字类型转换为字符串(处理学校标识码可能是数字的情况)
|
||||||
|
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
|
||||||
|
return cell.getDateCellValue().toString();
|
||||||
|
} else {
|
||||||
|
// 避免科学计数法,转为长整型再转字符串
|
||||||
|
double numericValue = cell.getNumericCellValue();
|
||||||
|
if (numericValue == (long) numericValue) {
|
||||||
|
return String.valueOf((long) numericValue);
|
||||||
|
} else {
|
||||||
|
return String.valueOf(numericValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case BOOLEAN:
|
||||||
|
return String.valueOf(cell.getBooleanCellValue());
|
||||||
|
case FORMULA:
|
||||||
|
return cell.getCellFormula();
|
||||||
|
case BLANK:
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
backend/src/main/java/com/bycrm/util/SecureKeyGenerator.java
Normal file
38
backend/src/main/java/com/bycrm/util/SecureKeyGenerator.java
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.bycrm.util;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全密钥生成工具
|
||||||
|
* 用于生成符合 JWT 规范的安全密钥
|
||||||
|
*/
|
||||||
|
public class SecureKeyGenerator {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 生成 HS512 算法的安全密钥
|
||||||
|
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
|
||||||
|
|
||||||
|
// 获取密钥的 Base64 编码
|
||||||
|
String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded());
|
||||||
|
|
||||||
|
System.out.println("=== JWT 安全密钥生成器 ===");
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("算法: HS512");
|
||||||
|
System.out.println("密钥长度: " + key.getEncoded().length * 8 + " 位");
|
||||||
|
System.out.println("密钥字节: " + key.getEncoded().length + " 字节");
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("Base64 编码的密钥(推荐用于生产环境):");
|
||||||
|
System.out.println(encodedKey);
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("请在 application.yml 中设置:");
|
||||||
|
System.out.println("jwt:");
|
||||||
|
System.out.println(" secret: " + encodedKey);
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("或者使用环境变量(更安全):");
|
||||||
|
System.out.println("JWT_SECRET=" + encodedKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -60,10 +61,10 @@ public class CustomerVO implements Serializable {
|
||||||
private String currentDealerName;
|
private String currentDealerName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保护期结束时间
|
* 保护期结束日期
|
||||||
*/
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDateTime protectEndDate;
|
private LocalDate protectEndDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -65,16 +66,16 @@ public class ReportVO implements Serializable {
|
||||||
private String rejectReason;
|
private String rejectReason;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保护期开始时间
|
* 保护期开始日期
|
||||||
*/
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDateTime protectStartDate;
|
private LocalDate protectStartDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保护期结束时间
|
* 保护期结束日期
|
||||||
*/
|
*/
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
private LocalDateTime protectEndDate;
|
private LocalDate protectEndDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 剩余保护天数
|
* 剩余保护天数
|
||||||
48
backend/src/main/java/com/bycrm/vo/SchoolVO.java
Normal file
48
backend/src/main/java/com/bycrm/vo/SchoolVO.java
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.bycrm.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SchoolVO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校标识码
|
||||||
|
*/
|
||||||
|
private String schoolCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 学校名称
|
||||||
|
*/
|
||||||
|
private String schoolName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所在地
|
||||||
|
*/
|
||||||
|
private String location;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
|
|
@ -9,9 +9,9 @@ spring:
|
||||||
datasource:
|
datasource:
|
||||||
type: com.alibaba.druid.pool.DruidDataSource
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
url: jdbc:mysql://localhost:3306/by_crm?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
url: jdbc:mysql://192.168.3.80:3306/by_crm?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
||||||
username: root
|
username: root
|
||||||
password: root
|
password: "Boyun@123"
|
||||||
druid:
|
druid:
|
||||||
initial-size: 5
|
initial-size: 5
|
||||||
min-idle: 5
|
min-idle: 5
|
||||||
|
|
@ -39,6 +39,10 @@ spring:
|
||||||
date-format: yyyy-MM-dd HH:mm:ss
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
default-property-inclusion: non_null
|
default-property-inclusion: non_null
|
||||||
|
|
||||||
|
mvc:
|
||||||
|
pathmatch:
|
||||||
|
matching-strategy: ant_path_matcher
|
||||||
|
|
||||||
# MyBatis 配置
|
# MyBatis 配置
|
||||||
mybatis:
|
mybatis:
|
||||||
mapper-locations: classpath:mapper/*.xml
|
mapper-locations: classpath:mapper/*.xml
|
||||||
|
|
@ -50,7 +54,9 @@ mybatis:
|
||||||
|
|
||||||
# JWT 配置
|
# JWT 配置
|
||||||
jwt:
|
jwt:
|
||||||
secret: by-crm-secret-key-2024-please-change-in-production
|
# HS512 算法要求密钥至少 64 字节(512 位)
|
||||||
|
# 生产环境请使用环境变量或密钥管理系统存储此密钥
|
||||||
|
secret: by-crm-jwt-secret-key-2024-hs512-requires-at-least-64-bytes-for-secure-signing-please-change-in-production-environment
|
||||||
expiration: 86400000 # 24小时,单位:毫秒
|
expiration: 86400000 # 24小时,单位:毫秒
|
||||||
|
|
||||||
# CRM 业务配置
|
# CRM 业务配置
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,13 @@
|
||||||
<result column="updated_at" property="updatedAt"/>
|
<result column="updated_at" property="updatedAt"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="CustomerVOResultMap" type="com.bycrm.vo.CustomerVO" extends="BaseResultMap">
|
||||||
|
<result column="status_desc" property="statusDesc"/>
|
||||||
|
<result column="current_dealer_id" property="currentDealerId"/>
|
||||||
|
<result column="current_dealer_name" property="currentDealerName"/>
|
||||||
|
<result column="protect_end_date" property="protectEndDate"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectById" resultMap="BaseResultMap">
|
<select id="selectById" resultMap="BaseResultMap">
|
||||||
SELECT * FROM crm_customer WHERE id = #{id}
|
SELECT * FROM crm_customer WHERE id = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -77,4 +84,36 @@
|
||||||
DELETE FROM crm_customer WHERE id = #{id}
|
DELETE FROM crm_customer WHERE id = #{id}
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<select id="selectPageWithDealer" resultMap="CustomerVOResultMap">
|
||||||
|
SELECT
|
||||||
|
c.*,
|
||||||
|
CASE c.status
|
||||||
|
WHEN 0 THEN '可报备'
|
||||||
|
WHEN 1 THEN '保护中'
|
||||||
|
WHEN 2 THEN '已失效'
|
||||||
|
ELSE '未知'
|
||||||
|
END AS status_desc,
|
||||||
|
r.dealer_id AS current_dealer_id,
|
||||||
|
d.name AS current_dealer_name,
|
||||||
|
r.protect_end_date
|
||||||
|
FROM crm_customer c
|
||||||
|
LEFT JOIN crm_report r ON c.id = r.customer_id
|
||||||
|
AND r.status = 1
|
||||||
|
AND r.protect_end_date > NOW()
|
||||||
|
LEFT JOIN crm_dealer d ON r.dealer_id = d.id
|
||||||
|
<where>
|
||||||
|
<if test="name != null and name != ''">
|
||||||
|
AND c.name LIKE CONCAT('%', #{name}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="industry != null and industry != ''">
|
||||||
|
AND c.industry = #{industry}
|
||||||
|
</if>
|
||||||
|
<if test="status != null">
|
||||||
|
AND c.status = #{status}
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY c.created_at DESC
|
||||||
|
LIMIT #{query.size} OFFSET #{query.offset}
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
<result column="status" property="status"/>
|
<result column="status" property="status"/>
|
||||||
<result column="created_at" property="createdAt"/>
|
<result column="created_at" property="createdAt"/>
|
||||||
<result column="updated_at" property="updatedAt"/>
|
<result column="updated_at" property="updatedAt"/>
|
||||||
|
<result column="user_id" property="userId"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectById" resultMap="BaseResultMap">
|
<select id="selectById" resultMap="BaseResultMap">
|
||||||
|
|
@ -24,19 +25,21 @@
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectList" resultMap="BaseResultMap">
|
<select id="selectList" resultMap="BaseResultMap">
|
||||||
SELECT * FROM crm_dealer
|
SELECT d.*, u.id as user_id
|
||||||
|
FROM crm_dealer d
|
||||||
|
LEFT JOIN crm_user u ON u.dealer_id = d.id
|
||||||
<where>
|
<where>
|
||||||
<if test="name != null and name != ''">
|
<if test="name != null and name != ''">
|
||||||
AND name LIKE CONCAT('%', #{name}, '%')
|
AND d.name LIKE CONCAT('%', #{name}, '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="code != null and code != ''">
|
<if test="code != null and code != ''">
|
||||||
AND code LIKE CONCAT('%', #{code}, '%')
|
AND d.code LIKE CONCAT('%', #{code}, '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="status != null">
|
<if test="status != null">
|
||||||
AND status = #{status}
|
AND d.status = #{status}
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
ORDER BY created_at DESC
|
ORDER BY d.created_at DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insert" parameterType="com.bycrm.entity.Dealer" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insert" parameterType="com.bycrm.entity.Dealer" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
|
@ -61,4 +64,8 @@
|
||||||
DELETE FROM crm_dealer WHERE id = #{id}
|
DELETE FROM crm_dealer WHERE id = #{id}
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<select id="count" resultType="java.lang.Long">
|
||||||
|
SELECT COUNT(*) FROM crm_dealer
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
|
|
@ -119,4 +119,23 @@
|
||||||
</foreach>
|
</foreach>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<select id="countByDealerId" resultType="java.lang.Long">
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM crm_report
|
||||||
|
<where>
|
||||||
|
<if test="dealerId != null">
|
||||||
|
AND dealer_id = #{dealerId}
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="countPendingByDealerId" resultType="java.lang.Long">
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM crm_report
|
||||||
|
WHERE status = 0
|
||||||
|
<if test="dealerId != null">
|
||||||
|
AND dealer_id = #{dealerId}
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
59
backend/src/main/resources/mapper/SchoolMapper.xml
Normal file
59
backend/src/main/resources/mapper/SchoolMapper.xml
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.bycrm.mapper.SchoolMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.bycrm.entity.School">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="school_code" property="schoolCode"/>
|
||||||
|
<result column="school_name" property="schoolName"/>
|
||||||
|
<result column="location" property="location"/>
|
||||||
|
<result column="created_at" property="createdAt"/>
|
||||||
|
<result column="updated_at" property="updatedAt"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<insert id="insert" parameterType="com.bycrm.entity.School" useGeneratedKeys="true" keyProperty="id">
|
||||||
|
INSERT INTO crm_school (school_code, school_name, location, created_at, updated_at)
|
||||||
|
VALUES (#{schoolCode}, #{schoolName}, #{location}, #{createdAt}, #{updatedAt})
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<insert id="batchInsert" parameterType="java.util.List">
|
||||||
|
INSERT INTO crm_school (school_code, school_name, location, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
<foreach collection="list" item="item" separator=",">
|
||||||
|
(#{item.schoolCode}, #{item.schoolName}, #{item.location}, #{item.createdAt}, #{item.updatedAt})
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="searchByName" resultMap="BaseResultMap">
|
||||||
|
SELECT id, school_code, school_name, location, created_at, updated_at
|
||||||
|
FROM crm_school
|
||||||
|
WHERE school_name LIKE CONCAT('%', #{keyword}, '%')
|
||||||
|
ORDER BY school_name
|
||||||
|
LIMIT 50
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByCode" resultMap="BaseResultMap">
|
||||||
|
SELECT id, school_code, school_name, location, created_at, updated_at
|
||||||
|
FROM crm_school
|
||||||
|
WHERE school_code = #{schoolCode}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findAll" resultMap="BaseResultMap">
|
||||||
|
SELECT id, school_code, school_name, location, created_at, updated_at
|
||||||
|
FROM crm_school
|
||||||
|
ORDER BY school_name
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByPage" resultMap="BaseResultMap">
|
||||||
|
SELECT id, school_code, school_name, location, created_at, updated_at
|
||||||
|
FROM crm_school
|
||||||
|
ORDER BY school_name
|
||||||
|
LIMIT #{offset}, #{limit}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="count" resultType="int">
|
||||||
|
SELECT COUNT(*) FROM crm_school
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
46
backend/src/main/resources/mapper/SystemConfigMapper.xml
Normal file
46
backend/src/main/resources/mapper/SystemConfigMapper.xml
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.bycrm.mapper.SystemConfigMapper">
|
||||||
|
|
||||||
|
<resultMap id="BaseResultMap" type="com.bycrm.entity.SystemConfig">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="config_key" property="configKey"/>
|
||||||
|
<result column="config_value" property="configValue"/>
|
||||||
|
<result column="config_label" property="configLabel"/>
|
||||||
|
<result column="config_type" property="configType"/>
|
||||||
|
<result column="description" property="description"/>
|
||||||
|
<result column="is_editable" property="isEditable"/>
|
||||||
|
<result column="created_at" property="createdAt"/>
|
||||||
|
<result column="updated_at" property="updatedAt"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="selectAll" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM crm_system_config ORDER BY id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByKey" resultMap="BaseResultMap">
|
||||||
|
SELECT * FROM crm_system_config WHERE config_key = #{configKey}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectValueByKey" resultType="java.lang.String">
|
||||||
|
SELECT config_value FROM crm_system_config WHERE config_key = #{configKey}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="update" parameterType="com.bycrm.entity.SystemConfig">
|
||||||
|
UPDATE crm_system_config
|
||||||
|
SET config_value = #{configValue},
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE config_key = #{configKey}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="batchUpdate">
|
||||||
|
<foreach collection="configs" item="config" separator=";">
|
||||||
|
UPDATE crm_system_config
|
||||||
|
SET config_value = #{config.configValue},
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE config_key = #{config.configKey}
|
||||||
|
</foreach>
|
||||||
|
</update>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
81
backend/target/classes/application.yml
Normal file
81
backend/target/classes/application.yml
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
servlet:
|
||||||
|
context-path: /api
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: by-crm
|
||||||
|
datasource:
|
||||||
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://192.168.3.80:3306/by_crm?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
||||||
|
username: root
|
||||||
|
password: "Boyun@123"
|
||||||
|
druid:
|
||||||
|
initial-size: 5
|
||||||
|
min-idle: 5
|
||||||
|
max-active: 20
|
||||||
|
max-wait: 60000
|
||||||
|
time-between-eviction-runs-millis: 60000
|
||||||
|
min-evictable-idle-time-millis: 300000
|
||||||
|
validation-query: SELECT 1
|
||||||
|
test-while-idle: true
|
||||||
|
test-on-borrow: false
|
||||||
|
test-on-return: false
|
||||||
|
filters: stat,wall
|
||||||
|
web-stat-filter:
|
||||||
|
enabled: true
|
||||||
|
url-pattern: /*
|
||||||
|
stat-view-servlet:
|
||||||
|
enabled: true
|
||||||
|
url-pattern: /druid/*
|
||||||
|
reset-enable: false
|
||||||
|
login-username: admin
|
||||||
|
login-password: admin123
|
||||||
|
|
||||||
|
jackson:
|
||||||
|
time-zone: GMT+8
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
|
default-property-inclusion: non_null
|
||||||
|
|
||||||
|
mvc:
|
||||||
|
pathmatch:
|
||||||
|
matching-strategy: ant_path_matcher
|
||||||
|
|
||||||
|
# MyBatis 配置
|
||||||
|
mybatis:
|
||||||
|
mapper-locations: classpath:mapper/*.xml
|
||||||
|
type-aliases-package: com.bycrm.entity
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
cache-enabled: false
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
|
||||||
|
# JWT 配置
|
||||||
|
jwt:
|
||||||
|
# HS512 算法要求密钥至少 64 字节(512 位)
|
||||||
|
# 生产环境请使用环境变量或密钥管理系统存储此密钥
|
||||||
|
secret: by-crm-jwt-secret-key-2024-hs512-requires-at-least-64-bytes-for-secure-signing-please-change-in-production-environment
|
||||||
|
expiration: 86400000 # 24小时,单位:毫秒
|
||||||
|
|
||||||
|
# CRM 业务配置
|
||||||
|
crm:
|
||||||
|
report:
|
||||||
|
ttl-days: 90 # 保护期天数
|
||||||
|
allow-overlap: false # 是否允许重叠报备(生产环境必须为 false)
|
||||||
|
|
||||||
|
# Swagger 配置
|
||||||
|
springfox:
|
||||||
|
documentation:
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.bycrm.mapper: debug
|
||||||
|
org.springframework.web: info
|
||||||
|
pattern:
|
||||||
|
console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
|
||||||
BIN
backend/target/classes/com/bycrm/ByCrmApplication.class
Normal file
BIN
backend/target/classes/com/bycrm/ByCrmApplication.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/annotations/AuthRequired.class
Normal file
BIN
backend/target/classes/com/bycrm/annotations/AuthRequired.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/common/Constants.class
Normal file
BIN
backend/target/classes/com/bycrm/common/Constants.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/common/PageResult.class
Normal file
BIN
backend/target/classes/com/bycrm/common/PageResult.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/common/Result.class
Normal file
BIN
backend/target/classes/com/bycrm/common/Result.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/config/AuthInterceptor.class
Normal file
BIN
backend/target/classes/com/bycrm/config/AuthInterceptor.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/config/CorsConfig.class
Normal file
BIN
backend/target/classes/com/bycrm/config/CorsConfig.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/config/MyBatisConfig.class
Normal file
BIN
backend/target/classes/com/bycrm/config/MyBatisConfig.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/config/SwaggerConfig.class
Normal file
BIN
backend/target/classes/com/bycrm/config/SwaggerConfig.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/config/WebMvcConfig.class
Normal file
BIN
backend/target/classes/com/bycrm/config/WebMvcConfig.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/target/classes/com/bycrm/controller/AuthController.class
Normal file
BIN
backend/target/classes/com/bycrm/controller/AuthController.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/target/classes/com/bycrm/dto/ChangePasswordDTO.class
Normal file
BIN
backend/target/classes/com/bycrm/dto/ChangePasswordDTO.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/dto/CustomerDTO.class
Normal file
BIN
backend/target/classes/com/bycrm/dto/CustomerDTO.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/target/classes/com/bycrm/dto/DealerDTO.class
Normal file
BIN
backend/target/classes/com/bycrm/dto/DealerDTO.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/dto/LoginDTO.class
Normal file
BIN
backend/target/classes/com/bycrm/dto/LoginDTO.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/dto/PageQuery.class
Normal file
BIN
backend/target/classes/com/bycrm/dto/PageQuery.class
Normal file
Binary file not shown.
BIN
backend/target/classes/com/bycrm/dto/ReportAuditDTO.class
Normal file
BIN
backend/target/classes/com/bycrm/dto/ReportAuditDTO.class
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user