初始化项目
This commit is contained in:
1 parent
45a1702948
commit
934b8aad40
155 files changed
+2649
-68
No files matched your search
@@ -0,0 +1,21 @@
|
||||
package com.bycrm;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* 经销商管理系统主启动类
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
public class ByCrmApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ByCrmApplication.class, args);
|
||||
System.out.println("\n========================================");
|
||||
System.out.println("经销商管理系统启动成功!");
|
||||
System.out.println("Swagger文档地址: http://localhost:8080/api/swagger-ui/index.html");
|
||||
System.out.println("========================================\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.bycrm.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 需要认证的注解
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AuthRequired {
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.bycrm.common;
|
||||
|
||||
/**
|
||||
* 系统常量
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
/**
|
||||
* JWT Token 前缀
|
||||
*/
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
/**
|
||||
* JWT Token Header
|
||||
*/
|
||||
public static final String TOKEN_HEADER = "Authorization";
|
||||
|
||||
/**
|
||||
* 用户信息在 Redis 中的前缀
|
||||
*/
|
||||
public static final String USER_TOKEN_PREFIX = "user:token:";
|
||||
|
||||
/**
|
||||
* 客户状态 - 可报备
|
||||
*/
|
||||
public static final int CUSTOMER_STATUS_AVAILABLE = 0;
|
||||
|
||||
/**
|
||||
* 客户状态 - 保护中
|
||||
*/
|
||||
public static final int CUSTOMER_STATUS_PROTECTED = 1;
|
||||
|
||||
/**
|
||||
* 客户状态 - 已失效
|
||||
*/
|
||||
public static final int CUSTOMER_STATUS_EXPIRED = 2;
|
||||
|
||||
/**
|
||||
* 报备状态 - 待审核
|
||||
*/
|
||||
public static final int REPORT_STATUS_PENDING = 0;
|
||||
|
||||
/**
|
||||
* 报备状态 - 已通过
|
||||
*/
|
||||
public static final int REPORT_STATUS_APPROVED = 1;
|
||||
|
||||
/**
|
||||
* 报备状态 - 已驳回
|
||||
*/
|
||||
public static final int REPORT_STATUS_REJECTED = 2;
|
||||
|
||||
/**
|
||||
* 报备状态 - 已失效
|
||||
*/
|
||||
public static final int REPORT_STATUS_EXPIRED = 3;
|
||||
|
||||
/**
|
||||
* 用户角色 - 管理员
|
||||
*/
|
||||
public static final int USER_ROLE_ADMIN = 0;
|
||||
|
||||
/**
|
||||
* 用户角色 - 经销商用户
|
||||
*/
|
||||
public static final int USER_ROLE_DEALER = 1;
|
||||
|
||||
/**
|
||||
* 用户状态 - 禁用
|
||||
*/
|
||||
public static final int USER_STATUS_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* 用户状态 - 启用
|
||||
*/
|
||||
public static final int USER_STATUS_ENABLED = 1;
|
||||
|
||||
/**
|
||||
* 经销商状态 - 禁用
|
||||
*/
|
||||
public static final int DEALER_STATUS_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* 经销商状态 - 启用
|
||||
*/
|
||||
public static final int DEALER_STATUS_ENABLED = 1;
|
||||
|
||||
/**
|
||||
* 默认保护期天数
|
||||
*/
|
||||
public static final int DEFAULT_PROTECT_DAYS = 90;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.bycrm.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果
|
||||
*/
|
||||
@Data
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 当前页数据
|
||||
*/
|
||||
private List<T> records;
|
||||
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
private Long current;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
private Long size;
|
||||
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
private Long pages;
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public PageResult(Long total, List<T> records, Long current, Long size) {
|
||||
this.total = total;
|
||||
this.records = records;
|
||||
this.current = current;
|
||||
this.size = size;
|
||||
this.pages = (total + size - 1) / size;
|
||||
}
|
||||
|
||||
public static <T> PageResult<T> of(Long total, List<T> records, Long current, Long size) {
|
||||
return new PageResult<>(total, records, current, size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.bycrm.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一响应结果
|
||||
*/
|
||||
@Data
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
|
||||
public Result() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Result(Integer code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回(无数据)
|
||||
*/
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(200, "操作成功", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回(有数据)
|
||||
*/
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(200, "操作成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功返回(自定义消息)
|
||||
*/
|
||||
public static <T> Result<T> success(String message, T data) {
|
||||
return new Result<>(200, message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回
|
||||
*/
|
||||
public static <T> Result<T> error(String message) {
|
||||
return new Result<>(500, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败返回(自定义状态码)
|
||||
*/
|
||||
public static <T> Result<T> error(Integer code, String message) {
|
||||
return new Result<>(code, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未授权返回
|
||||
*/
|
||||
public static <T> Result<T> unauthorized(String message) {
|
||||
return new Result<>(401, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止访问返回
|
||||
*/
|
||||
public static <T> Result<T> forbidden(String message) {
|
||||
return new Result<>(403, message, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.bycrm.config;
|
||||
|
||||
import com.bycrm.common.Constants;
|
||||
import com.bycrm.util.JwtUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 认证拦截器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
|
||||
public AuthInterceptor(JwtUtil jwtUtil) {
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 设置当前用户ID到请求属性中
|
||||
String token = getTokenFromRequest(request);
|
||||
|
||||
if (token != null && jwtUtil.validateToken(token)) {
|
||||
Long userId = jwtUtil.getUserIdFromToken(token);
|
||||
request.setAttribute("currentUserId", userId);
|
||||
request.setAttribute("currentUserRole", jwtUtil.getRoleFromToken(token));
|
||||
request.setAttribute("currentUserDealerId", jwtUtil.getDealerIdFromToken(token));
|
||||
return true;
|
||||
}
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":401,\"message\":\"未授权,请先登录\"}");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中获取 Token
|
||||
*/
|
||||
private String getTokenFromRequest(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader(Constants.TOKEN_HEADER);
|
||||
if (bearerToken != null && bearerToken.startsWith(Constants.TOKEN_PREFIX)) {
|
||||
return bearerToken.substring(Constants.TOKEN_PREFIX.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.bycrm.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.bycrm.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis 配置
|
||||
*/
|
||||
@Configuration
|
||||
public class MyBatisConfig {
|
||||
|
||||
// MyBatis 配置主要通过 application.yml 配置
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.bycrm.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.oas.annotations.EnableOpenApi;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
|
||||
/**
|
||||
* Swagger 配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableOpenApi
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.bycrm.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("经销商管理系统 API 文档")
|
||||
.description("经销商管理系统 RESTful API 接口文档")
|
||||
.contact(new Contact("BY CRM", "", ""))
|
||||
.version("1.0.0")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.bycrm.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Web MVC 配置
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
private final AuthInterceptor authInterceptor;
|
||||
|
||||
public WebMvcConfig(AuthInterceptor authInterceptor) {
|
||||
this.authInterceptor = authInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(authInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(Arrays.asList(
|
||||
"/auth/login",
|
||||
"/swagger-ui/**",
|
||||
"/swagger-resources/**",
|
||||
"/v3/api-docs/**",
|
||||
"/webjars/**",
|
||||
"/error",
|
||||
"/druid/**"
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.bycrm.controller;
|
||||
|
||||
import com.bycrm.common.Result;
|
||||
import com.bycrm.dto.ChangePasswordDTO;
|
||||
import com.bycrm.dto.LoginDTO;
|
||||
import com.bycrm.dto.ResetPasswordDTO;
|
||||
import com.bycrm.service.UserService;
|
||||
import com.bycrm.vo.UserInfoVO;
|
||||
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("/auth")
|
||||
public class AuthController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public AuthController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
@ApiOperation("用户登录")
|
||||
@PostMapping("/login")
|
||||
public Result<LoginResponse> login(@RequestBody LoginDTO loginDTO) {
|
||||
String token = userService.login(loginDTO);
|
||||
return Result.success(new LoginResponse(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
@ApiOperation("获取当前用户信息")
|
||||
@GetMapping("/user/info")
|
||||
public Result<UserInfoVO> getCurrentUser(HttpServletRequest request) {
|
||||
String token = getTokenFromRequest(request);
|
||||
UserInfoVO user = userService.getCurrentUser(token);
|
||||
return Result.success(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户退出
|
||||
*/
|
||||
@ApiOperation("用户退出")
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout() {
|
||||
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
|
||||
*/
|
||||
private String getTokenFromRequest(HttpServletRequest request) {
|
||||
String authorization = request.getHeader("Authorization");
|
||||
if (authorization != null && authorization.startsWith("Bearer ")) {
|
||||
return authorization.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录响应
|
||||
*/
|
||||
@lombok.Data
|
||||
static class LoginResponse {
|
||||
private String token;
|
||||
|
||||
public LoginResponse(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.bycrm.controller;
|
||||
|
||||
import com.bycrm.common.PageResult;
|
||||
import com.bycrm.common.Result;
|
||||
import com.bycrm.dto.CustomerDTO;
|
||||
import com.bycrm.dto.PageQuery;
|
||||
import com.bycrm.entity.Customer;
|
||||
import com.bycrm.service.CustomerService;
|
||||
import com.bycrm.vo.CustomerVO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客户控制器
|
||||
*/
|
||||
@Api(tags = "客户管理")
|
||||
@RestController
|
||||
@RequestMapping("/customer")
|
||||
public class CustomerController {
|
||||
|
||||
private final CustomerService customerService;
|
||||
|
||||
public CustomerController(CustomerService customerService) {
|
||||
this.customerService = customerService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询客户
|
||||
*/
|
||||
@ApiOperation("分页查询客户")
|
||||
@GetMapping("/page")
|
||||
public Result<PageResult<CustomerVO>> getCustomerPage(
|
||||
@ApiParam("当前页") @RequestParam(defaultValue = "1") Long current,
|
||||
@ApiParam("每页大小") @RequestParam(defaultValue = "10") Long size,
|
||||
@ApiParam("客户名称") @RequestParam(required = false) String name,
|
||||
@ApiParam("所属行业") @RequestParam(required = false) String industry,
|
||||
@ApiParam("状态") @RequestParam(required = false) Integer status) {
|
||||
PageQuery query = new PageQuery();
|
||||
query.setCurrent(current);
|
||||
query.setSize(size);
|
||||
|
||||
PageResult<CustomerVO> result = customerService.getCustomerPage(query, name, industry, status);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取客户详情
|
||||
*/
|
||||
@ApiOperation("根据ID获取客户详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<CustomerVO> getCustomerById(@ApiParam("客户ID") @PathVariable Long id) {
|
||||
CustomerVO customer = customerService.getCustomerById(id);
|
||||
return Result.success(customer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建客户
|
||||
*/
|
||||
@ApiOperation("创建客户")
|
||||
@PostMapping
|
||||
public Result<Customer> createCustomer(@RequestBody CustomerDTO customerDTO, HttpServletRequest request) {
|
||||
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||
Customer customer = customerService.createCustomer(customerDTO, currentUserId);
|
||||
return Result.success(customer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客户
|
||||
*/
|
||||
@ApiOperation("更新客户")
|
||||
@PutMapping("/{id}")
|
||||
public Result<Void> updateCustomer(
|
||||
@ApiParam("客户ID") @PathVariable Long id,
|
||||
@RequestBody CustomerDTO customerDTO,
|
||||
HttpServletRequest request) {
|
||||
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||
customerService.updateCustomer(id, customerDTO, currentUserId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客户
|
||||
*/
|
||||
@ApiOperation("删除客户")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteCustomer(@ApiParam("客户ID") @PathVariable Long id, HttpServletRequest request) {
|
||||
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||
customerService.deleteCustomer(id, currentUserId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称搜索客户(用于客户唯一性校验)
|
||||
*/
|
||||
@ApiOperation("根据名称搜索客户")
|
||||
@GetMapping("/search")
|
||||
public Result<List<Customer>> searchByName(@ApiParam("客户名称") @RequestParam String name) {
|
||||
List<Customer> customers = customerService.searchByName(name);
|
||||
return Result.success(customers);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.bycrm.controller;
|
||||
|
||||
import com.bycrm.common.Result;
|
||||
import com.bycrm.dto.DealerDTO;
|
||||
import com.bycrm.entity.Dealer;
|
||||
import com.bycrm.service.DealerService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 经销商控制器
|
||||
*/
|
||||
@Api(tags = "经销商管理")
|
||||
@RestController
|
||||
@RequestMapping("/dealer")
|
||||
public class DealerController {
|
||||
|
||||
private final DealerService dealerService;
|
||||
|
||||
public DealerController(DealerService dealerService) {
|
||||
this.dealerService = dealerService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有经销商
|
||||
*/
|
||||
@ApiOperation("查询所有经销商")
|
||||
@GetMapping("/list")
|
||||
public Result<List<Dealer>> getDealerList(
|
||||
@ApiParam("经销商名称") @RequestParam(required = false) String name,
|
||||
@ApiParam("经销商账号") @RequestParam(required = false) String code,
|
||||
@ApiParam("状态") @RequestParam(required = false) Integer status) {
|
||||
List<Dealer> dealers = dealerService.getDealerList(name, code, status);
|
||||
return Result.success(dealers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取经销商
|
||||
*/
|
||||
@ApiOperation("根据ID获取经销商")
|
||||
@GetMapping("/{id}")
|
||||
public Result<Dealer> getDealerById(@ApiParam("经销商ID") @PathVariable Long id) {
|
||||
Dealer dealer = dealerService.getDealerById(id);
|
||||
return Result.success(dealer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建经销商
|
||||
*/
|
||||
@ApiOperation("创建经销商")
|
||||
@PostMapping
|
||||
public Result<Void> createDealer(@RequestBody DealerDTO dealerDTO) {
|
||||
dealerService.createDealer(dealerDTO);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新经销商
|
||||
*/
|
||||
@ApiOperation("更新经销商")
|
||||
@PutMapping("/{id}")
|
||||
public Result<Void> updateDealer(
|
||||
@ApiParam("经销商ID") @PathVariable Long id,
|
||||
@RequestBody DealerDTO dealerDTO) {
|
||||
dealerService.updateDealer(id, dealerDTO);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除经销商
|
||||
*/
|
||||
@ApiOperation("删除经销商")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteDealer(@ApiParam("经销商ID") @PathVariable Long id) {
|
||||
dealerService.deleteDealer(id);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.bycrm.controller;
|
||||
|
||||
import com.bycrm.common.PageResult;
|
||||
import com.bycrm.common.Result;
|
||||
import com.bycrm.dto.PageQuery;
|
||||
import com.bycrm.dto.ReportAuditDTO;
|
||||
import com.bycrm.dto.ReportDTO;
|
||||
import com.bycrm.service.ReportService;
|
||||
import com.bycrm.vo.ReportVO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 报备控制器
|
||||
*/
|
||||
@Api(tags = "报备管理")
|
||||
@RestController
|
||||
@RequestMapping("/report")
|
||||
public class ReportController {
|
||||
|
||||
private final ReportService reportService;
|
||||
|
||||
public ReportController(ReportService reportService) {
|
||||
this.reportService = reportService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询报备
|
||||
*/
|
||||
@ApiOperation("分页查询报备")
|
||||
@GetMapping("/page")
|
||||
public Result<PageResult<ReportVO>> getReportPage(
|
||||
@ApiParam("当前页") @RequestParam(defaultValue = "1") Long current,
|
||||
@ApiParam("每页大小") @RequestParam(defaultValue = "10") Long size,
|
||||
@ApiParam("经销商ID") @RequestParam(required = false) Long dealerId,
|
||||
@ApiParam("经销商名称") @RequestParam(required = false) String dealerName,
|
||||
@ApiParam("客户名称") @RequestParam(required = false) String customerName,
|
||||
@ApiParam("状态") @RequestParam(required = false) Integer status,
|
||||
HttpServletRequest request) {
|
||||
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||
|
||||
PageQuery query = new PageQuery();
|
||||
query.setCurrent(current);
|
||||
query.setSize(size);
|
||||
|
||||
PageResult<ReportVO> result = reportService.getReportPage(query, dealerId, dealerName, customerName, status, currentUserId);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取报备详情
|
||||
*/
|
||||
@ApiOperation("根据ID获取报备详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<ReportVO> getReportById(@ApiParam("报备ID") @PathVariable Long id) {
|
||||
ReportVO report = reportService.getReportById(id);
|
||||
return Result.success(report);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建报备
|
||||
*/
|
||||
@ApiOperation("创建报备")
|
||||
@PostMapping
|
||||
public Result<Void> createReport(@RequestBody ReportDTO reportDTO, HttpServletRequest request) {
|
||||
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||
reportService.createReport(reportDTO, currentUserId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核报备
|
||||
*/
|
||||
@ApiOperation("审核报备")
|
||||
@PutMapping("/{id}/audit")
|
||||
public Result<Void> auditReport(
|
||||
@ApiParam("报备ID") @PathVariable Long id,
|
||||
@RequestBody ReportAuditDTO auditDTO,
|
||||
HttpServletRequest request) {
|
||||
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||
reportService.auditReport(id, auditDTO, currentUserId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回报备
|
||||
*/
|
||||
@ApiOperation("撤回报备")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> withdrawReport(@ApiParam("报备ID") @PathVariable Long id, HttpServletRequest request) {
|
||||
Long currentUserId = (Long) request.getAttribute("currentUserId");
|
||||
reportService.withdrawReport(id, currentUserId);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,36 @@
|
||||
package com.bycrm.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 客户请求 DTO
|
||||
*/
|
||||
@Data
|
||||
public class CustomerDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 客户名称
|
||||
*/
|
||||
@NotBlank(message = "客户名称不能为空")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 所属行业
|
||||
*/
|
||||
private String industry;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bycrm.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 经销商请求 DTO
|
||||
*/
|
||||
@Data
|
||||
public class DealerDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 经销商名称
|
||||
*/
|
||||
@NotBlank(message = "经销商名称不能为空")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 经销商编码
|
||||
*/
|
||||
@NotBlank(message = "经销商账号不能为空")
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
@NotBlank(message = "联系人不能为空")
|
||||
private String contactPerson;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
@NotBlank(message = "联系电话不能为空")
|
||||
private String contactPhone;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 初始密码(新增用户时可设置)
|
||||
*/
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.bycrm.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 登录请求 DTO
|
||||
*/
|
||||
@Data
|
||||
public class LoginDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.bycrm.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 分页查询基类
|
||||
*/
|
||||
@Data
|
||||
public class PageQuery implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
private Long current = 1L;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
private Long size = 10L;
|
||||
|
||||
/**
|
||||
* 偏移量(自动计算,不需要手动设置)
|
||||
*/
|
||||
private Long offset;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
private String orderBy;
|
||||
|
||||
/**
|
||||
* 排序方式:asc/desc
|
||||
*/
|
||||
private String orderDirection = "desc";
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.bycrm.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 报备审核 DTO
|
||||
*/
|
||||
@Data
|
||||
public class ReportAuditDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 报备ID
|
||||
*/
|
||||
@NotNull(message = "报备ID不能为空")
|
||||
private Long reportId;
|
||||
|
||||
/**
|
||||
* 是否通过
|
||||
*/
|
||||
@NotNull(message = "审核结果不能为空")
|
||||
private Boolean approved;
|
||||
|
||||
/**
|
||||
* 驳回原因
|
||||
*/
|
||||
private String rejectReason;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.bycrm.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 报备请求 DTO
|
||||
*/
|
||||
@Data
|
||||
public class ReportDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
@NotNull(message = "客户ID不能为空")
|
||||
private Long customerId;
|
||||
|
||||
/**
|
||||
* 报备说明
|
||||
*/
|
||||
private String description;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.bycrm.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 客户实体
|
||||
*/
|
||||
@Data
|
||||
public class Customer implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 客户名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 所属行业
|
||||
*/
|
||||
private String industry;
|
||||
|
||||
/**
|
||||
* 状态:0-可报备 1-保护中 2-已失效
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.bycrm.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 经销商实体
|
||||
*/
|
||||
@Data
|
||||
public class Dealer implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 经销商ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 经销商名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 经销商编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 联系人
|
||||
*/
|
||||
private String contactPerson;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String contactPhone;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态:0-禁用 1-启用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 关联的用户ID(用于重置密码等操作)
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.bycrm.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据字典实体
|
||||
*/
|
||||
@Data
|
||||
public class Dict implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 字典ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 字典编码
|
||||
*/
|
||||
private String dictCode;
|
||||
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
private String dictName;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 字典项列表(不存储在数据库中)
|
||||
*/
|
||||
private transient List<DictItem> items;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.bycrm.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据字典项实体
|
||||
*/
|
||||
@Data
|
||||
public class DictItem implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 字典项ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 字典ID
|
||||
*/
|
||||
private Long dictId;
|
||||
|
||||
/**
|
||||
* 字典项标签
|
||||
*/
|
||||
private String itemLabel;
|
||||
|
||||
/**
|
||||
* 字典项值
|
||||
*/
|
||||
private String itemValue;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.bycrm.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 操作日志实体
|
||||
*/
|
||||
@Data
|
||||
public class OperationLog implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 日志ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名(不存储在数据库中)
|
||||
*/
|
||||
private transient String username;
|
||||
|
||||
/**
|
||||
* 模块名称
|
||||
*/
|
||||
private String module;
|
||||
|
||||
/**
|
||||
* 操作类型
|
||||
*/
|
||||
private String operation;
|
||||
|
||||
/**
|
||||
* 操作描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* IP地址
|
||||
*/
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.bycrm.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 报备实体
|
||||
*/
|
||||
@Data
|
||||
public class Report implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 报备ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 经销商ID
|
||||
*/
|
||||
private Long dealerId;
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
private Long customerId;
|
||||
|
||||
/**
|
||||
* 报备说明
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态:0-待审核 1-已通过 2-已驳回 3-已失效
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 驳回原因
|
||||
*/
|
||||
private String rejectReason;
|
||||
|
||||
/**
|
||||
* 保护期开始日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate protectStartDate;
|
||||
|
||||
/**
|
||||
* 保护期结束日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate protectEndDate;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 关联查询字段 - 经销商名称
|
||||
*/
|
||||
private String dealerName;
|
||||
|
||||
/**
|
||||
* 关联查询字段 - 客户名称
|
||||
*/
|
||||
private String customerName;
|
||||
|
||||
/**
|
||||
* 关联查询字段 - 客户电话
|
||||
*/
|
||||
private String customerPhone;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.bycrm.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户实体
|
||||
*/
|
||||
@Data
|
||||
public class User implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码(BCrypt加密)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 关联经销商ID(管理员为NULL)
|
||||
*/
|
||||
private Long dealerId;
|
||||
|
||||
/**
|
||||
* 角色:0-管理员 1-经销商用户
|
||||
*/
|
||||
private Integer role;
|
||||
|
||||
/**
|
||||
* 状态:0-禁用 1-启用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 关联查询字段 - 经销商名称
|
||||
*/
|
||||
private String dealerName;
|
||||
|
||||
/**
|
||||
* Token(不存储在数据库中,仅用于返回)
|
||||
*/
|
||||
private transient String token;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.bycrm.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private final Integer code;
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = 500;
|
||||
}
|
||||
|
||||
public BusinessException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public BusinessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = 500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.bycrm.exception;
|
||||
|
||||
import com.bycrm.common.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Result<Void> handleBusinessException(BusinessException e) {
|
||||
log.error("业务异常:{}", e.getMessage());
|
||||
return Result.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
String message = e.getBindingResult().getFieldErrors().stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.collect(Collectors.joining(", "));
|
||||
log.error("参数校验异常:{}", message);
|
||||
return Result.error(400, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 约束违反异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
|
||||
String message = e.getConstraintViolations().stream()
|
||||
.map(ConstraintViolation::getMessage)
|
||||
.collect(Collectors.joining(", "));
|
||||
log.error("约束违反异常:{}", message);
|
||||
return Result.error(400, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result<Void> handleException(Exception e) {
|
||||
log.error("系统异常", e);
|
||||
return Result.error("系统异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.bycrm.mapper;
|
||||
|
||||
import com.bycrm.entity.Customer;
|
||||
import com.bycrm.dto.PageQuery;
|
||||
import com.bycrm.vo.CustomerVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客户 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface CustomerMapper {
|
||||
|
||||
/**
|
||||
* 根据ID查询客户
|
||||
*/
|
||||
Customer selectById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 根据名称模糊查询客户(用于客户唯一性校验)
|
||||
*/
|
||||
List<Customer> selectByNameLike(@Param("name") String name);
|
||||
|
||||
/**
|
||||
* 分页查询客户
|
||||
*/
|
||||
List<Customer> selectPage(@Param("query") PageQuery query, @Param("name") String name,
|
||||
@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);
|
||||
|
||||
/**
|
||||
* 查询客户总数
|
||||
*/
|
||||
Long countPage(@Param("name") String name, @Param("industry") String industry, @Param("status") Integer status);
|
||||
|
||||
/**
|
||||
* 插入客户
|
||||
*/
|
||||
int insert(Customer customer);
|
||||
|
||||
/**
|
||||
* 更新客户
|
||||
*/
|
||||
int update(Customer customer);
|
||||
|
||||
/**
|
||||
* 删除客户
|
||||
*/
|
||||
int deleteById(@Param("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.bycrm.mapper;
|
||||
|
||||
import com.bycrm.entity.Dealer;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 经销商 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface DealerMapper {
|
||||
|
||||
/**
|
||||
* 根据ID查询经销商
|
||||
*/
|
||||
Dealer selectById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 根据编码查询经销商
|
||||
*/
|
||||
Dealer selectByCode(@Param("code") String code);
|
||||
|
||||
/**
|
||||
* 查询所有经销商
|
||||
*/
|
||||
List<Dealer> selectList(@Param("name") String name, @Param("code") String code, @Param("status") Integer status);
|
||||
|
||||
/**
|
||||
* 插入经销商
|
||||
*/
|
||||
int insert(Dealer dealer);
|
||||
|
||||
/**
|
||||
* 更新经销商
|
||||
*/
|
||||
int update(Dealer dealer);
|
||||
|
||||
/**
|
||||
* 删除经销商
|
||||
*/
|
||||
int deleteById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 统计经销商总数
|
||||
*/
|
||||
Long count();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.bycrm.mapper;
|
||||
|
||||
import com.bycrm.entity.Report;
|
||||
import com.bycrm.dto.PageQuery;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 报备 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface ReportMapper {
|
||||
|
||||
/**
|
||||
* 根据ID查询报备(含关联信息)
|
||||
*/
|
||||
Report selectById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 查询客户的有效报备
|
||||
*/
|
||||
Report selectValidByCustomerId(@Param("customerId") Long customerId);
|
||||
|
||||
/**
|
||||
* 分页查询报备
|
||||
*/
|
||||
List<Report> selectPage(@Param("query") PageQuery query, @Param("dealerId") Long dealerId,
|
||||
@Param("dealerName") String dealerName, @Param("customerName") String customerName,
|
||||
@Param("status") Integer status);
|
||||
|
||||
/**
|
||||
* 查询报备总数
|
||||
*/
|
||||
Long countPage(@Param("dealerId") Long dealerId, @Param("dealerName") String dealerName,
|
||||
@Param("customerName") String customerName, @Param("status") Integer status);
|
||||
|
||||
/**
|
||||
* 插入报备
|
||||
*/
|
||||
int insert(Report report);
|
||||
|
||||
/**
|
||||
* 更新报备
|
||||
*/
|
||||
int update(Report report);
|
||||
|
||||
/**
|
||||
* 删除报备
|
||||
*/
|
||||
int deleteById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 查询即将过期的报备(定时任务使用)
|
||||
*/
|
||||
List<Report> selectExpiringReports(@Param("endDate") String endDate);
|
||||
|
||||
/**
|
||||
* 批量更新报备状态为已失效
|
||||
*/
|
||||
int batchUpdateExpired(@Param("ids") List<Long> ids);
|
||||
|
||||
/**
|
||||
* 统计报备总数(根据经销商ID过滤)
|
||||
*/
|
||||
Long countByDealerId(@Param("dealerId") Long dealerId);
|
||||
|
||||
/**
|
||||
* 统计待审核报备数量(根据经销商ID过滤)
|
||||
*/
|
||||
Long countPendingByDealerId(@Param("dealerId") Long dealerId);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.bycrm.mapper;
|
||||
|
||||
import com.bycrm.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户
|
||||
*/
|
||||
User selectByUsername(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* 根据ID查询用户
|
||||
*/
|
||||
User selectById(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 查询所有用户
|
||||
*/
|
||||
List<User> selectList();
|
||||
|
||||
/**
|
||||
* 插入用户
|
||||
*/
|
||||
int insert(User user);
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
int update(User user);
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
int deleteById(@Param("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.bycrm.service;
|
||||
|
||||
import com.bycrm.common.PageResult;
|
||||
import com.bycrm.dto.CustomerDTO;
|
||||
import com.bycrm.dto.PageQuery;
|
||||
import com.bycrm.entity.Customer;
|
||||
import com.bycrm.vo.CustomerVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客户服务接口
|
||||
*/
|
||||
public interface CustomerService {
|
||||
|
||||
/**
|
||||
* 分页查询客户
|
||||
*/
|
||||
PageResult<CustomerVO> getCustomerPage(PageQuery query, String name, String industry, Integer status);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取客户详情
|
||||
*/
|
||||
CustomerVO getCustomerById(Long id);
|
||||
|
||||
/**
|
||||
* 创建客户
|
||||
*/
|
||||
Customer createCustomer(CustomerDTO customerDTO, Long currentUserId);
|
||||
|
||||
/**
|
||||
* 更新客户
|
||||
*/
|
||||
void updateCustomer(Long id, CustomerDTO customerDTO, Long currentUserId);
|
||||
|
||||
/**
|
||||
* 删除客户
|
||||
*/
|
||||
void deleteCustomer(Long id, Long currentUserId);
|
||||
|
||||
/**
|
||||
* 根据名称模糊查询客户(用于客户唯一性校验)
|
||||
*/
|
||||
List<Customer> searchByName(String name);
|
||||
|
||||
/**
|
||||
* 校验客户名称是否重复
|
||||
*/
|
||||
void checkCustomerNameDuplicate(String name);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.bycrm.service;
|
||||
|
||||
import com.bycrm.dto.DealerDTO;
|
||||
import com.bycrm.entity.Dealer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 经销商服务接口
|
||||
*/
|
||||
public interface DealerService {
|
||||
|
||||
/**
|
||||
* 查询所有经销商
|
||||
*/
|
||||
List<Dealer> getDealerList(String name, String code, Integer status);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取经销商
|
||||
*/
|
||||
Dealer getDealerById(Long id);
|
||||
|
||||
/**
|
||||
* 创建经销商
|
||||
*/
|
||||
void createDealer(DealerDTO dealerDTO);
|
||||
|
||||
/**
|
||||
* 更新经销商
|
||||
*/
|
||||
void updateDealer(Long id, DealerDTO dealerDTO);
|
||||
|
||||
/**
|
||||
* 删除经销商
|
||||
*/
|
||||
void deleteDealer(Long id);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.bycrm.service;
|
||||
|
||||
import com.bycrm.common.PageResult;
|
||||
import com.bycrm.dto.PageQuery;
|
||||
import com.bycrm.dto.ReportAuditDTO;
|
||||
import com.bycrm.dto.ReportDTO;
|
||||
import com.bycrm.vo.ReportVO;
|
||||
|
||||
/**
|
||||
* 报备服务接口
|
||||
*/
|
||||
public interface ReportService {
|
||||
|
||||
/**
|
||||
* 分页查询报备
|
||||
*/
|
||||
PageResult<ReportVO> getReportPage(PageQuery query, Long dealerId, String dealerName,
|
||||
String customerName, Integer status, Long currentUserId);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取报备详情
|
||||
*/
|
||||
ReportVO getReportById(Long id);
|
||||
|
||||
/**
|
||||
* 创建报备
|
||||
*/
|
||||
void createReport(ReportDTO reportDTO, Long currentUserId);
|
||||
|
||||
/**
|
||||
* 审核报备
|
||||
*/
|
||||
void auditReport(Long id, ReportAuditDTO auditDTO, Long currentUserId);
|
||||
|
||||
/**
|
||||
* 撤回报备
|
||||
*/
|
||||
void withdrawReport(Long id, Long currentUserId);
|
||||
|
||||
/**
|
||||
* 处理过期报备(定时任务调用)
|
||||
*/
|
||||
void handleExpiredReports();
|
||||
|
||||
/**
|
||||
* 统计报备总数(根据经销商ID过滤)
|
||||
*/
|
||||
Long countByDealerId(Long dealerId);
|
||||
|
||||
/**
|
||||
* 统计待审核报备数量(根据经销商ID过滤)
|
||||
*/
|
||||
Long countPendingByDealerId(Long dealerId);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.bycrm.service;
|
||||
|
||||
import com.bycrm.dto.ChangePasswordDTO;
|
||||
import com.bycrm.dto.LoginDTO;
|
||||
import com.bycrm.dto.ResetPasswordDTO;
|
||||
import com.bycrm.entity.User;
|
||||
import com.bycrm.vo.UserInfoVO;
|
||||
|
||||
/**
|
||||
* 用户服务接口
|
||||
*/
|
||||
public interface UserService {
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
String login(LoginDTO loginDTO);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取用户信息
|
||||
*/
|
||||
User getUserById(Long id);
|
||||
|
||||
/**
|
||||
* 根据用户名获取用户信息
|
||||
*/
|
||||
User getUserByUsername(String username);
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*/
|
||||
UserInfoVO getCurrentUser(String token);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
void changePassword(Long userId, ChangePasswordDTO dto);
|
||||
|
||||
/**
|
||||
* 重置密码(管理员功能)
|
||||
*/
|
||||
void resetPassword(ResetPasswordDTO dto);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.bycrm.service.impl;
|
||||
|
||||
import com.bycrm.common.Constants;
|
||||
import com.bycrm.common.PageResult;
|
||||
import com.bycrm.dto.CustomerDTO;
|
||||
import com.bycrm.dto.PageQuery;
|
||||
import com.bycrm.entity.Customer;
|
||||
import com.bycrm.exception.BusinessException;
|
||||
import com.bycrm.mapper.CustomerMapper;
|
||||
import com.bycrm.mapper.UserMapper;
|
||||
import com.bycrm.service.CustomerService;
|
||||
import com.bycrm.vo.CustomerVO;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 客户服务实现
|
||||
*/
|
||||
@Service
|
||||
public class CustomerServiceImpl implements CustomerService {
|
||||
|
||||
private final CustomerMapper customerMapper;
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public CustomerServiceImpl(CustomerMapper customerMapper, UserMapper userMapper) {
|
||||
this.customerMapper = customerMapper;
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<CustomerVO> getCustomerPage(PageQuery query, String name, String industry, Integer status) {
|
||||
// 计算偏移量
|
||||
query.setOffset((query.getCurrent() - 1) * query.getSize());
|
||||
|
||||
// 使用新的查询方法,直接返回 CustomerVO(包含经销商信息)
|
||||
List<CustomerVO> customerVOs = customerMapper.selectPageWithDealer(query, name, industry, status);
|
||||
Long total = customerMapper.countPage(name, industry, status);
|
||||
|
||||
return PageResult.of(total, customerVOs, query.getCurrent(), query.getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomerVO getCustomerById(Long id) {
|
||||
Customer customer = customerMapper.selectById(id);
|
||||
if (customer == null) {
|
||||
throw new BusinessException("客户不存在");
|
||||
}
|
||||
return convertToVO(customer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Customer createCustomer(CustomerDTO customerDTO, Long currentUserId) {
|
||||
// 校验客户名称是否重复
|
||||
checkCustomerNameDuplicate(customerDTO.getName());
|
||||
|
||||
Customer customer = new Customer();
|
||||
BeanUtils.copyProperties(customerDTO, customer);
|
||||
customer.setStatus(Constants.CUSTOMER_STATUS_AVAILABLE);
|
||||
customer.setCreatedAt(LocalDateTime.now());
|
||||
customer.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
customerMapper.insert(customer);
|
||||
return customer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateCustomer(Long id, CustomerDTO customerDTO, Long currentUserId) {
|
||||
Customer existingCustomer = customerMapper.selectById(id);
|
||||
if (existingCustomer == null) {
|
||||
throw new BusinessException("客户不存在");
|
||||
}
|
||||
|
||||
// 如果修改了名称,需要检查新名称是否与其他客户重复
|
||||
if (!existingCustomer.getName().equals(customerDTO.getName())) {
|
||||
checkCustomerNameDuplicate(customerDTO.getName());
|
||||
}
|
||||
|
||||
Customer customer = new Customer();
|
||||
BeanUtils.copyProperties(customerDTO, customer);
|
||||
customer.setId(id);
|
||||
customer.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
customerMapper.update(customer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteCustomer(Long id, Long currentUserId) {
|
||||
Customer customer = customerMapper.selectById(id);
|
||||
if (customer == null) {
|
||||
throw new BusinessException("客户不存在");
|
||||
}
|
||||
|
||||
if (customer.getStatus() == Constants.CUSTOMER_STATUS_PROTECTED) {
|
||||
throw new BusinessException("客户处于保护期中,无法删除");
|
||||
}
|
||||
|
||||
customerMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Customer> searchByName(String name) {
|
||||
return customerMapper.selectByNameLike(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCustomerNameDuplicate(String name) {
|
||||
List<Customer> existingCustomers = customerMapper.selectByNameLike(name);
|
||||
if (existingCustomers.stream().anyMatch(c -> c.getName().equals(name))) {
|
||||
throw new BusinessException("客户名称已存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private CustomerVO convertToVO(Customer customer) {
|
||||
CustomerVO vo = new CustomerVO();
|
||||
BeanUtils.copyProperties(customer, vo);
|
||||
|
||||
// 设置状态描述
|
||||
switch (customer.getStatus()) {
|
||||
case Constants.CUSTOMER_STATUS_AVAILABLE:
|
||||
vo.setStatusDesc("可报备");
|
||||
break;
|
||||
case Constants.CUSTOMER_STATUS_PROTECTED:
|
||||
vo.setStatusDesc("保护中");
|
||||
break;
|
||||
case Constants.CUSTOMER_STATUS_EXPIRED:
|
||||
vo.setStatusDesc("已失效");
|
||||
break;
|
||||
default:
|
||||
vo.setStatusDesc("未知");
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.bycrm.service.impl;
|
||||
|
||||
import com.bycrm.common.Constants;
|
||||
import com.bycrm.dto.DealerDTO;
|
||||
import com.bycrm.entity.Dealer;
|
||||
import com.bycrm.entity.User;
|
||||
import com.bycrm.exception.BusinessException;
|
||||
import com.bycrm.mapper.DealerMapper;
|
||||
import com.bycrm.mapper.UserMapper;
|
||||
import com.bycrm.service.DealerService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 经销商服务实现
|
||||
*/
|
||||
@Service
|
||||
public class DealerServiceImpl implements DealerService {
|
||||
|
||||
private final DealerMapper dealerMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
public DealerServiceImpl(DealerMapper dealerMapper, UserMapper userMapper) {
|
||||
this.dealerMapper = dealerMapper;
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Dealer> getDealerList(String name, String code, Integer status) {
|
||||
return dealerMapper.selectList(name, code, status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dealer getDealerById(Long id) {
|
||||
Dealer dealer = dealerMapper.selectById(id);
|
||||
if (dealer == null) {
|
||||
throw new BusinessException("经销商不存在");
|
||||
}
|
||||
return dealer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createDealer(DealerDTO dealerDTO) {
|
||||
// 检查编码是否重复
|
||||
Dealer existingDealer = dealerMapper.selectByCode(dealerDTO.getCode());
|
||||
if (existingDealer != null) {
|
||||
throw new BusinessException("经销商账号已存在");
|
||||
}
|
||||
|
||||
Dealer dealer = new Dealer();
|
||||
BeanUtils.copyProperties(dealerDTO, dealer);
|
||||
dealer.setCreatedAt(LocalDateTime.now());
|
||||
dealer.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
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
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateDealer(Long id, DealerDTO dealerDTO) {
|
||||
Dealer existingDealer = dealerMapper.selectById(id);
|
||||
if (existingDealer == null) {
|
||||
throw new BusinessException("经销商不存在");
|
||||
}
|
||||
|
||||
// 如果修改了编码,需要检查新编码是否与其他经销商重复
|
||||
if (!existingDealer.getCode().equals(dealerDTO.getCode())) {
|
||||
Dealer codeDealer = dealerMapper.selectByCode(dealerDTO.getCode());
|
||||
if (codeDealer != null && !codeDealer.getId().equals(id)) {
|
||||
throw new BusinessException("经销商账号已存在");
|
||||
}
|
||||
}
|
||||
|
||||
Dealer dealer = new Dealer();
|
||||
BeanUtils.copyProperties(dealerDTO, dealer);
|
||||
dealer.setId(id);
|
||||
dealer.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
dealerMapper.update(dealer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteDealer(Long id) {
|
||||
Dealer dealer = dealerMapper.selectById(id);
|
||||
if (dealer == null) {
|
||||
throw new BusinessException("经销商不存在");
|
||||
}
|
||||
|
||||
dealerMapper.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package com.bycrm.service.impl;
|
||||
|
||||
import com.bycrm.common.Constants;
|
||||
import com.bycrm.common.PageResult;
|
||||
import com.bycrm.dto.PageQuery;
|
||||
import com.bycrm.dto.ReportAuditDTO;
|
||||
import com.bycrm.dto.ReportDTO;
|
||||
import com.bycrm.entity.Report;
|
||||
import com.bycrm.entity.Customer;
|
||||
import com.bycrm.entity.User;
|
||||
import com.bycrm.exception.BusinessException;
|
||||
import com.bycrm.mapper.CustomerMapper;
|
||||
import com.bycrm.mapper.ReportMapper;
|
||||
import com.bycrm.mapper.UserMapper;
|
||||
import com.bycrm.service.ReportService;
|
||||
import com.bycrm.service.SystemConfigService;
|
||||
import com.bycrm.vo.ReportVO;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 报备服务实现
|
||||
*/
|
||||
@Service
|
||||
public class ReportServiceImpl implements ReportService {
|
||||
|
||||
private final ReportMapper reportMapper;
|
||||
private final CustomerMapper customerMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final SystemConfigService systemConfigService;
|
||||
|
||||
public ReportServiceImpl(ReportMapper reportMapper,
|
||||
CustomerMapper customerMapper,
|
||||
UserMapper userMapper,
|
||||
SystemConfigService systemConfigService) {
|
||||
this.reportMapper = reportMapper;
|
||||
this.customerMapper = customerMapper;
|
||||
this.userMapper = userMapper;
|
||||
this.systemConfigService = systemConfigService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ReportVO> getReportPage(PageQuery query, Long dealerId, String dealerName,
|
||||
String customerName, Integer status, Long currentUserId) {
|
||||
// 获取当前用户
|
||||
User currentUser = userMapper.selectById(currentUserId);
|
||||
|
||||
// 如果是经销商用户,只能查看自己的报备
|
||||
if (currentUser.getRole() == Constants.USER_ROLE_DEALER) {
|
||||
dealerId = currentUser.getDealerId();
|
||||
}
|
||||
|
||||
// 计算偏移量
|
||||
query.setOffset((query.getCurrent() - 1) * query.getSize());
|
||||
|
||||
List<Report> reports = reportMapper.selectPage(query, dealerId, dealerName, customerName, status);
|
||||
Long total = reportMapper.countPage(dealerId, dealerName, customerName, status);
|
||||
|
||||
List<ReportVO> voList = reports.stream().map(this::convertToVO).collect(Collectors.toList());
|
||||
|
||||
return PageResult.of(total, voList, query.getCurrent(), query.getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReportVO getReportById(Long id) {
|
||||
Report report = reportMapper.selectById(id);
|
||||
if (report == null) {
|
||||
throw new BusinessException("报备不存在");
|
||||
}
|
||||
return convertToVO(report);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createReport(ReportDTO reportDTO, Long currentUserId) {
|
||||
// 获取当前用户
|
||||
User currentUser = userMapper.selectById(currentUserId);
|
||||
if (currentUser.getDealerId() == null) {
|
||||
throw new BusinessException("您未关联经销商,无法提交报备");
|
||||
}
|
||||
|
||||
// 检查客户是否存在
|
||||
Customer customer = customerMapper.selectById(reportDTO.getCustomerId());
|
||||
if (customer == null) {
|
||||
throw new BusinessException("客户不存在");
|
||||
}
|
||||
|
||||
// 防撞单校验:检查该客户是否已存在有效报备
|
||||
Report existingReport = reportMapper.selectValidByCustomerId(reportDTO.getCustomerId());
|
||||
Boolean allowOverlap = systemConfigService.getBooleanConfigValue("report.allow.overlap", false);
|
||||
if (existingReport != null && !allowOverlap) {
|
||||
throw new BusinessException("该客户已被其他经销商报备,无法重复报备");
|
||||
}
|
||||
|
||||
// 创建报备
|
||||
Report report = new Report();
|
||||
report.setDealerId(currentUser.getDealerId());
|
||||
report.setCustomerId(reportDTO.getCustomerId());
|
||||
report.setDescription(reportDTO.getDescription());
|
||||
report.setStatus(Constants.REPORT_STATUS_PENDING);
|
||||
report.setCreatedAt(LocalDateTime.now());
|
||||
report.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
reportMapper.insert(report);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void auditReport(Long id, ReportAuditDTO auditDTO, Long currentUserId) {
|
||||
// 获取当前用户
|
||||
User currentUser = userMapper.selectById(currentUserId);
|
||||
if (currentUser.getRole() != Constants.USER_ROLE_ADMIN) {
|
||||
throw new BusinessException("只有管理员才能审核报备");
|
||||
}
|
||||
|
||||
Report report = reportMapper.selectById(id);
|
||||
if (report == null) {
|
||||
throw new BusinessException("报备不存在");
|
||||
}
|
||||
|
||||
if (report.getStatus() != Constants.REPORT_STATUS_PENDING) {
|
||||
throw new BusinessException("该报备已被审核,无需重复操作");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
if (auditDTO.getApproved()) {
|
||||
// 审核通过
|
||||
report.setStatus(Constants.REPORT_STATUS_APPROVED);
|
||||
LocalDate today = LocalDate.now();
|
||||
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.setStatus(Constants.CUSTOMER_STATUS_PROTECTED);
|
||||
customerMapper.update(customer);
|
||||
} else {
|
||||
// 审核驳回
|
||||
report.setStatus(Constants.REPORT_STATUS_REJECTED);
|
||||
report.setRejectReason(auditDTO.getRejectReason());
|
||||
}
|
||||
|
||||
report.setUpdatedAt(now);
|
||||
reportMapper.update(report);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void withdrawReport(Long id, Long currentUserId) {
|
||||
User currentUser = userMapper.selectById(currentUserId);
|
||||
|
||||
Report report = reportMapper.selectById(id);
|
||||
if (report == null) {
|
||||
throw new BusinessException("报备不存在");
|
||||
}
|
||||
|
||||
// 只有报备提交者才能撤回
|
||||
if (!report.getDealerId().equals(currentUser.getDealerId())) {
|
||||
throw new BusinessException("您只能撤回自己的报备");
|
||||
}
|
||||
|
||||
if (report.getStatus() != Constants.REPORT_STATUS_PENDING) {
|
||||
throw new BusinessException("只能撤回待审核的报备");
|
||||
}
|
||||
|
||||
reportMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void handleExpiredReports() {
|
||||
// 查询所有保护期已到的报备
|
||||
String endDate = LocalDate.now().toString();
|
||||
List<Report> expiringReports = reportMapper.selectExpiringReports(endDate);
|
||||
|
||||
if (expiringReports.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 批量更新报备状态
|
||||
List<Long> ids = expiringReports.stream().map(Report::getId).collect(Collectors.toList());
|
||||
reportMapper.batchUpdateExpired(ids);
|
||||
|
||||
// 批量更新客户状态为可报备
|
||||
expiringReports.forEach(report -> {
|
||||
Customer customer = customerMapper.selectById(report.getCustomerId());
|
||||
if (customer != null && customer.getStatus() == Constants.CUSTOMER_STATUS_PROTECTED) {
|
||||
customer.setStatus(Constants.CUSTOMER_STATUS_AVAILABLE);
|
||||
customerMapper.update(customer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countByDealerId(Long dealerId) {
|
||||
return reportMapper.countByDealerId(dealerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countPendingByDealerId(Long dealerId) {
|
||||
return reportMapper.countPendingByDealerId(dealerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 VO
|
||||
*/
|
||||
private ReportVO convertToVO(Report report) {
|
||||
ReportVO vo = new ReportVO();
|
||||
BeanUtils.copyProperties(report, vo);
|
||||
|
||||
// 设置状态描述
|
||||
switch (report.getStatus()) {
|
||||
case Constants.REPORT_STATUS_PENDING:
|
||||
vo.setStatusDesc("待审核");
|
||||
break;
|
||||
case Constants.REPORT_STATUS_APPROVED:
|
||||
vo.setStatusDesc("已通过");
|
||||
// 计算剩余保护天数
|
||||
if (report.getProtectEndDate() != null) {
|
||||
long days = ChronoUnit.DAYS.between(LocalDate.now(), report.getProtectEndDate());
|
||||
vo.setRemainDays((int) Math.max(0, days));
|
||||
}
|
||||
break;
|
||||
case Constants.REPORT_STATUS_REJECTED:
|
||||
vo.setStatusDesc("已驳回");
|
||||
break;
|
||||
case Constants.REPORT_STATUS_EXPIRED:
|
||||
vo.setStatusDesc("已失效");
|
||||
break;
|
||||
default:
|
||||
vo.setStatusDesc("未知");
|
||||
}
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.bycrm.service.impl;
|
||||
|
||||
import com.bycrm.common.Constants;
|
||||
import com.bycrm.dto.ChangePasswordDTO;
|
||||
import com.bycrm.dto.ResetPasswordDTO;
|
||||
import com.bycrm.entity.User;
|
||||
import com.bycrm.exception.BusinessException;
|
||||
import com.bycrm.mapper.UserMapper;
|
||||
import com.bycrm.service.UserService;
|
||||
import com.bycrm.util.JwtUtil;
|
||||
import com.bycrm.vo.UserInfoVO;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 用户服务实现
|
||||
*/
|
||||
@Service
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
private final UserMapper userMapper;
|
||||
private final JwtUtil jwtUtil;
|
||||
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
public UserServiceImpl(UserMapper userMapper, JwtUtil jwtUtil) {
|
||||
this.userMapper = userMapper;
|
||||
this.jwtUtil = jwtUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String login(com.bycrm.dto.LoginDTO loginDTO) {
|
||||
User user = userMapper.selectByUsername(loginDTO.getUsername());
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户名或密码错误");
|
||||
}
|
||||
|
||||
if (user.getStatus() == Constants.USER_STATUS_DISABLED) {
|
||||
throw new BusinessException("用户已被禁用");
|
||||
}
|
||||
|
||||
if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
|
||||
throw new BusinessException("用户名或密码错误");
|
||||
}
|
||||
|
||||
return jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole(), user.getDealerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUserById(Long id) {
|
||||
return userMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUserByUsername(String username) {
|
||||
return userMapper.selectByUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserInfoVO getCurrentUser(String token) {
|
||||
Long userId = jwtUtil.getUserIdFromToken(token);
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
UserInfoVO vo = new UserInfoVO();
|
||||
vo.setUserId(user.getId());
|
||||
vo.setUsername(user.getUsername());
|
||||
vo.setRealName(user.getRealName());
|
||||
vo.setDealerId(user.getDealerId());
|
||||
vo.setDealerName(user.getDealerName());
|
||||
vo.setRole(user.getRole());
|
||||
vo.setRoleDesc(user.getRole() == Constants.USER_ROLE_ADMIN ? "管理员" : "经销商用户");
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.bycrm.task;
|
||||
|
||||
import com.bycrm.service.ReportService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 报备过期处理定时任务
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ReportExpireTask {
|
||||
|
||||
private final ReportService reportService;
|
||||
|
||||
public ReportExpireTask(ReportService reportService) {
|
||||
this.reportService = reportService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 每天凌晨1点执行,处理过期的报备
|
||||
*/
|
||||
@Scheduled(cron = "0 0 1 * * ?")
|
||||
public void handleExpiredReports() {
|
||||
log.info("开始处理过期报备...");
|
||||
try {
|
||||
reportService.handleExpiredReports();
|
||||
log.info("过期报备处理完成");
|
||||
} catch (Exception e) {
|
||||
log.error("处理过期报备失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.bycrm.util;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT 工具类
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private Long expiration;
|
||||
|
||||
/**
|
||||
* 生成 Token
|
||||
*/
|
||||
public String generateToken(Long userId, String username, Integer role, Long dealerId) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("userId", userId);
|
||||
claims.put("username", username);
|
||||
claims.put("role", role);
|
||||
claims.put("dealerId", dealerId);
|
||||
return generateToken(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Token
|
||||
*/
|
||||
public String generateToken(Map<String, Object> claims) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expiration);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiryDate)
|
||||
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中获取 Claims
|
||||
*/
|
||||
public Claims getClaimsFromToken(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSigningKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中获取用户ID
|
||||
*/
|
||||
public Long getUserIdFromToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
return claims.get("userId", Long.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中获取用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
return claims.get("username", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中获取角色
|
||||
*/
|
||||
public Integer getRoleFromToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
return claims.get("role", Integer.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中获取经销商ID
|
||||
*/
|
||||
public Long getDealerIdFromToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
return claims.get("dealerId", Long.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 Token 是否过期
|
||||
*/
|
||||
public boolean isTokenExpired(String token) {
|
||||
try {
|
||||
Date expiration = getClaimsFromToken(token).getExpiration();
|
||||
return expiration.before(new Date());
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 Token
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
return !isTokenExpired(token);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名密钥
|
||||
*/
|
||||
private SecretKey getSigningKey() {
|
||||
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.bycrm.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 客户详情 VO
|
||||
*/
|
||||
@Data
|
||||
public class CustomerVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 客户名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 所属行业
|
||||
*/
|
||||
private String industry;
|
||||
|
||||
/**
|
||||
* 状态:0-可报备 1-保护中 2-已失效
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 状态描述
|
||||
*/
|
||||
private String statusDesc;
|
||||
|
||||
/**
|
||||
* 当前报备经销商ID
|
||||
*/
|
||||
private Long currentDealerId;
|
||||
|
||||
/**
|
||||
* 当前报备经销商名称
|
||||
*/
|
||||
private String currentDealerName;
|
||||
|
||||
/**
|
||||
* 保护期结束日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate protectEndDate;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.bycrm.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 报备详情 VO
|
||||
*/
|
||||
@Data
|
||||
public class ReportVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 报备ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 经销商ID
|
||||
*/
|
||||
private Long dealerId;
|
||||
|
||||
/**
|
||||
* 经销商名称
|
||||
*/
|
||||
private String dealerName;
|
||||
|
||||
/**
|
||||
* 客户ID
|
||||
*/
|
||||
private Long customerId;
|
||||
|
||||
/**
|
||||
* 客户名称
|
||||
*/
|
||||
private String customerName;
|
||||
|
||||
/**
|
||||
* 客户电话
|
||||
*/
|
||||
private String customerPhone;
|
||||
|
||||
/**
|
||||
* 报备说明
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 状态:0-待审核 1-已通过 2-已驳回 3-已失效
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 状态描述
|
||||
*/
|
||||
private String statusDesc;
|
||||
|
||||
/**
|
||||
* 驳回原因
|
||||
*/
|
||||
private String rejectReason;
|
||||
|
||||
/**
|
||||
* 保护期开始日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate protectStartDate;
|
||||
|
||||
/**
|
||||
* 保护期结束日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private LocalDate protectEndDate;
|
||||
|
||||
/**
|
||||
* 剩余保护天数
|
||||
*/
|
||||
private Integer remainDays;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.bycrm.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户信息 VO
|
||||
*/
|
||||
@Data
|
||||
public class UserInfoVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 经销商ID
|
||||
*/
|
||||
private Long dealerId;
|
||||
|
||||
/**
|
||||
* 经销商名称
|
||||
*/
|
||||
private String dealerName;
|
||||
|
||||
/**
|
||||
* 角色:0-管理员 1-经销商用户
|
||||
*/
|
||||
private Integer role;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private String roleDesc;
|
||||
}
|
||||
Reference in new issue
Block a user