Commit 613af047 by daijie

初创,搭建custom服务java迁移 基础框架,

parents
.idea
*.iml
*/target
*/*/target/
*/logs
/web
.classpath
.factorypath
.project
.settings
*.log
/logs/
### 分层调用链路
```
HTTP请求
[WebApp层] (接收请求)
[App层] (业务编排)
[Domain层] (领域逻辑)
[DAL层] (数据访问)
[DAL层] (数据库操作)
```
### 功能模块
```
├─ xxx-webapp
│ ├─com.jomalls.xxx.webapp ## 适配层:负责外部(UI、服务)发起的请求操作的处理
│ │ ├─ web ## 适配web发起的请求
│ │ │ ├─ vo
│ │ │ ├─ controller
│ │ ├─ app ## 适配app发起的请求
│ │ │ ├─ vo
│ │ │ ├─ controller
├─xxx-core ## 公共模块后期,可提炼为独立的jar引用
├─xxx-app
│ ├─com.jomalls.xxx.app ## 应用服务层:处理controller发起的业务流程的组合、编排,调度、事件驱动
│ │ ├─ event ## 事件驱动处理
│ │ ├─ schedule ## 定时调度
│ │ ├─ service ## 业务流程的组合编排
├─xxx-domain ## 领域服务层:
│ ├─com.jomalls.xxx.dal ## DAL数据访问层
│ │ ├─ entity ## 数据库实体
│ │ ├─ mapper ## ORM 实体映射接口
│ ├─com.jomalls.xxx.domain ## 领域服务层
│ │ ├─ bo ## 业务实体
│ │ ├─ service ## 领域服务接口
├─xxx-integrate ## 集成层(或者防腐层): 主要负责对外部接口的管理,可做熔断、降级等操作
│ ├─com.jomalls.xxx.integrate
│ │ ├─ feign ## 远程调用接口
│ │ ├─ dto
├─xxx-client ## 可选 对外提供的SDK客户端
├─xxx-starter ## 启动包
│ ├─com.jomalls.xxx
│ │ ├─ config ## 配置
│ │ ├─ XXXApplication ## 启动类
└─其他模块
└─
```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>custom-server-app</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server-integrate</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server-domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
package com.jomalls.custom.app.constant;
import lombok.Getter;
/**
* @Author: Lizh
* @Date: 2026/5/28 09:27
* @Description: 状态码枚举类
* @Version: 1.0
*/
@Getter
public enum CodeEnum {
/**
* 请求成功
*/
SUCCESS(200, "OK"),
/**
* 服务器内部错误
*/
FAIL(500, "Internal Server Error"),
/**
* 资源未找到
*/
NOT_FOUND(404, "Not Found"),
/**
* 未授权访问
*/
UNAUTHORIZED(401, "Unauthorized"),
/**
* 禁止访问
*/
FORBIDDEN(403, "Forbidden"),
/**
* 错误的请求
*/
BAD_REQUEST(400, "Bad Request");
/**
* -- GETTER --
* 获取状态码
*
* @return 状态码
*/
private final int code;
/**
* -- GETTER --
* 获取状态消息描述
*
* @return 状态消息描述
*/
private final String msg;
/**
* 构造方法
*
* @param code 状态码
* @param msg 状态消息描述
*/
CodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
package com.jomalls.custom.app.constant;
/**
* @Author: Lizh
* @Date: 2026/5/28 09:29
* @Description: 基础常量定义
* @Version: 1.0
*/
public class Constants {
/**
* map初始化大小
*/
public static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
}
package com.jomalls.custom.app.constant;
/**
* 返回状态码
*/
public class HttpStatus {
/**
* 操作成功
*/
public static final int SUCCESS = 200;
/**
* 对象创建成功
*/
public static final int CREATED = 201;
/**
* 请求已经被接受
*/
public static final int ACCEPTED = 202;
/**
* 操作已经执行成功,但是没有返回数据
*/
public static final int NO_CONTENT = 204;
/**
* 资源已被移除
*/
public static final int MOVED_PERM = 301;
/**
* 重定向
*/
public static final int SEE_OTHER = 303;
/**
* 资源没有被修改
*/
public static final int NOT_MODIFIED = 304;
/**
* 参数列表错误(缺少,格式不匹配)
*/
public static final int BAD_REQUEST = 400;
/**
* 未授权
*/
public static final int UNAUTHORIZED = 401;
/**
* 访问受限,授权过期
*/
public static final int FORBIDDEN = 403;
/**
* 资源,服务未找到
*/
public static final int NOT_FOUND = 404;
/**
* 不允许的http方法
*/
public static final int BAD_METHOD = 405;
/**
* 资源冲突,或者资源被锁
*/
public static final int CONFLICT = 409;
/**
* 不支持的数据,媒体类型
*/
public static final int UNSUPPORTED_TYPE = 415;
/**
* 系统内部错误
*/
public static final int ERROR = 500;
/**
* 接口未实现
*/
public static final int NOT_IMPLEMENTED = 501;
/**
* 系统警告消息
*/
public static final int WARN = 601;
}
package com.jomalls.custom.app.constant;
/**
* 用户常量信息
*/
public class UserConstants {
/**
* 平台内系统用户的唯一标志
*/
public static final String SYS_USER = "SYS_USER";
/**
* 正常状态
*/
public static final String NORMAL = "0";
/**
* 异常状态
*/
public static final String EXCEPTION = "1";
/**
* 用户封禁状态
*/
public static final String USER_DISABLE = "1";
/**
* 角色正常状态
*/
public static final String ROLE_NORMAL = "0";
/**
* 角色封禁状态
*/
public static final String ROLE_DISABLE = "1";
/**
* 部门正常状态
*/
public static final String DEPT_NORMAL = "0";
/**
* 部门停用状态
*/
public static final String DEPT_DISABLE = "1";
/**
* 字典正常状态
*/
public static final String DICT_NORMAL = "0";
/**
* 是否为系统默认(是)
*/
public static final String YES = "Y";
/**
* 是否菜单外链(是)
*/
public static final String YES_FRAME = "0";
/**
* 是否菜单外链(否)
*/
public static final String NO_FRAME = "1";
/**
* 菜单类型(目录)
*/
public static final String TYPE_DIR = "M";
/**
* 菜单类型(菜单)
*/
public static final String TYPE_MENU = "C";
/**
* 菜单类型(按钮)
*/
public static final String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
public final static String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
public final static String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
public final static String INNER_LINK = "InnerLink";
/**
* 校验是否唯一的返回标识
*/
public final static boolean UNIQUE = true;
public final static boolean NOT_UNIQUE = false;
/**
* 用户名长度限制
*/
public static final int USERNAME_MIN_LENGTH = 2;
public static final int USERNAME_MAX_LENGTH = 20;
/**
* 密码长度限制
*/
public static final int PASSWORD_MIN_LENGTH = 5;
public static final int PASSWORD_MAX_LENGTH = 20;
}
package com.jomalls.custom.app.exception;
import lombok.Getter;
/**
* @Author: Lizh
* @Date: 2026/5/28 10:27
* @Description: 业务异常
* @Version: 1.0
*/
public final class ServiceException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
@Getter
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
* <p>
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
@Getter
private String detailMessage;
/**
* 空构造方法,避免反序列化问题
*/
public ServiceException() {
}
public ServiceException(String message) {
this.message = message;
}
public ServiceException(String message, Integer code) {
this.message = message;
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public ServiceException setMessage(String message) {
this.message = message;
return this;
}
public ServiceException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
}
\ No newline at end of file
package com.jomalls.custom.app.model;
import com.jomalls.custom.page.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author Lizh
* createDate 2023/12/1 14:18
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysUserPageVO extends PageRequest implements Serializable {
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long id;
/**
* 用户名
*/
@Schema(description = "username")
private String username;
/**
* 密码
*/
@Schema(description = "password")
private String password;
/**
* 邮箱
*/
@Schema(description = "email")
private String email;
/**
* 手机号
*/
@Schema(description = "phone")
private String phone;
/**
* 状态:0-禁用,1-启用
*/
@Schema(description = "status")
private Integer status;
/**
* 创建时间
*/
@Schema(description = "created_at")
private LocalDateTime createdAt;
/**
* 更新时间
*/
@Schema(description = "updated_at")
private LocalDateTime updatedAt;
}
\ No newline at end of file
package com.jomalls.custom.app.model;
import com.jomalls.custom.page.PageRequest;
import lombok.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* @author Lizh
* createDate 2023/12/1 14:18
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysUserVO implements Serializable {
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long id;
/**
* 用户名
*/
@Schema(description = "username")
private String username;
/**
* 密码
*/
@Schema(description = "password")
private String password;
/**
* 邮箱
*/
@Schema(description = "email")
private String email;
/**
* 手机号
*/
@Schema(description = "phone")
private String phone;
/**
* 状态:0-禁用,1-启用
*/
@Schema(description = "status")
private Integer status;
/**
* 创建时间
*/
@Schema(description = "created_at")
private LocalDateTime createdAt;
/**
* 更新时间
*/
@Schema(description = "updated_at")
private LocalDateTime updatedAt;
}
\ No newline at end of file
package com.jomalls.custom.app.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.model.SysUserPageVO;
import com.jomalls.custom.app.model.SysUserVO;
import java.util.List;
/**
* 用户服务接口
*/
public interface SysUserService {
/**
* 列表查询接口
*
* @param sysUser 条件model
* @return list集合
*/
List<SysUserVO> list(SysUserVO sysUser);
/**
* 根据条件查询分页列表接口
*
* @param sysUserPage 分页入参model
* @return 分页对象
*/
IPage<SysUserVO> pageList(SysUserPageVO sysUserPage);
/**
* 根据id查询详情
*
* @param id 主键
* @return 实体model
*/
SysUserVO info(Long id);
/**
* 保存对象
*
* @param sysUser 保存对象
*/
void save(SysUserVO sysUser);
/**
* 根据id修改对象
*
* @param sysUser 修改对象
*/
void updateById(SysUserVO sysUser);
/**
* 根据主键ID进行删除
*
* @param id 主键
*/
void deleteById(Long id);
}
\ No newline at end of file
package com.jomalls.custom.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.exception.ServiceException;
import com.jomalls.custom.app.model.SysUserPageVO;
import com.jomalls.custom.app.model.SysUserVO;
import com.jomalls.custom.app.utils.BeanMapper;
import com.jomalls.custom.app.utils.CustomAsserts;
import com.jomalls.custom.dal.entity.SysUserEntity;
import com.jomalls.custom.domain.service.SysUserDomainService;
import com.jomalls.custom.app.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
/**
* 用户服务实现类
*/
@Slf4j
@Service
public class SysUserServiceImpl implements SysUserService {
private final SysUserDomainService sysUserDomainService;
@Autowired
public SysUserServiceImpl(SysUserDomainService sysUserDomainService) {
this.sysUserDomainService = sysUserDomainService;
}
@Override
public List<SysUserVO> list(SysUserVO sysUser) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
// 根据业务条件组装入参
if (null != sysUser) {
queryWrapper.lambda().like(StringUtils.isNotBlank(sysUser.getUsername()),
SysUserEntity::getUsername, sysUser.getUsername());
}
List<SysUserEntity> list = sysUserDomainService.list(queryWrapper);
return list.stream().map(e -> BeanMapper.mapper().convert(e, SysUserVO.class)).collect(Collectors.toList());
}
@Override
public IPage<SysUserVO> pageList(SysUserPageVO sysUserPage) {
CustomAsserts.nonNull(sysUserPage, "分页查询参数不能为空");
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
// 根据业务条件组装入参
queryWrapper.lambda().like(StringUtils.isNotBlank(sysUserPage.getUsername()),
SysUserEntity::getUsername, sysUserPage.getUsername());
IPage<SysUserEntity> page = sysUserDomainService.selectPage(queryWrapper, sysUserPage);
return page.convert(e -> BeanMapper.mapper().convert(e, SysUserVO.class));
}
@Override
public SysUserVO info(Long id) {
CustomAsserts.nonNull(id, "主键id不能为空");
SysUserEntity sysUser = sysUserDomainService.getById(id);
return BeanMapper.mapper().convert(sysUser, SysUserVO.class);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void save(SysUserVO sysUser) {
CustomAsserts.nonNull(sysUser, "实体对象不能为空");
SysUserEntity sysUserEntity = BeanMapper.mapper().convert(sysUser, SysUserEntity.class);
try {
sysUserDomainService.save(sysUserEntity);
} catch (DuplicateKeyException e) {
log.info("[ AppInfoServiceImpl save ] 应用编码/应用APK重复,请调整后再试!", e);
throw new ServiceException("应用编码/应用APK重复,请调整后再试!");
} catch (DataIntegrityViolationException e) {
if (e.getCause() != null && e.getCause().getMessage().contains("duplicate key")) {
throw new ServiceException("应用编码/应用APK重复,请调整后再试!");
}
throw new ServiceException(e.getCause() == null ? "操作数据库异常:" + e.getMessage() : e.getCause().getMessage());
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void updateById(SysUserVO sysUser) {
CustomAsserts.nonNull(sysUser, "实体对象不能为空");
SysUserEntity appInfo = BeanMapper.mapper().convert(sysUser, SysUserEntity.class);
try {
sysUserDomainService.updateById(appInfo);
} catch (DuplicateKeyException e) {
log.info("[ AppInfoServiceImpl updateById ] 应用编码/应用APK重复,请调整后再试!", e);
throw new ServiceException("应用编码/应用APK重复,请调整后再试!");
} catch (DataIntegrityViolationException e) {
if (e.getCause() != null && e.getCause().getMessage().contains("duplicate key")) {
throw new ServiceException("应用编码/应用APK重复,请调整后再试!");
}
throw new ServiceException(e.getCause() == null ? "操作数据库异常:" + e.getMessage() : e.getCause().getMessage());
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteById(Long id) {
CustomAsserts.nonNull(id, "主键id不能为空");
SysUserVO sysUser = info(id);
if (sysUser == null) {
throw new ServiceException("应用信息不存在!");
}
sysUserDomainService.removeById(id);
}
}
\ No newline at end of file
package com.jomalls.custom.app.utils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.util.ArrayList;
import java.util.List;
/**
* Bean映射工具类
* 使用Jackson实现,支持复杂嵌套对象转换
*
* 支持的复杂场景:
* 1. 嵌套对象(对象内包含另一个对象)
* 2. 集合类型(List, Set, Map)
* 3. 继承关系
* 4. Java 8日期时间类型(LocalDate, LocalDateTime等)
*
* @Author: Lizh
* @Date: 2026/5/28 11:29
* @Version: 1.0
*/
public class BeanMapper {
/**
* Jackson ObjectMapper配置
*/
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
static {
// 注册Java 8时间模块,支持LocalDate, LocalDateTime等
OBJECT_MAPPER.registerModule(new JavaTimeModule());
// 禁用日期时间戳格式,使用ISO-8601格式
OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 忽略未知属性,避免目标类缺少字段时报错
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 允许空对象转换
OBJECT_MAPPER.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 允许单值包装数组
OBJECT_MAPPER.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
}
private BeanMapper() {
}
public static BeanMapper mapper() {
return new BeanMapper();
}
/**
* 将源对象转换为目标类型
* 支持嵌套对象、集合等复杂结构
*
* @param source 源对象
* @param destinationClass 目标类型
* @param <S> 源类型
* @param <D> 目标类型
* @return 转换后的目标对象
*/
public <S, D> D convert(S source, Class<D> destinationClass) {
if (source == null) {
return null;
}
try {
return OBJECT_MAPPER.convertValue(source, destinationClass);
} catch (Exception e) {
throw new RuntimeException("BeanMapper convert failed: " +
"source=" + source.getClass().getName() +
", destination=" + destinationClass.getName(), e);
}
}
/**
* 将源对象列表转换为目标类型列表
*
* @param sourceList 源对象列表
* @param destinationClass 目标类型
* @param <S> 源类型
* @param <D> 目标类型
* @return 转换后的目标对象列表
*/
public <S, D> List<D> convertList(List<S> sourceList, Class<D> destinationClass) {
if (sourceList == null || sourceList.isEmpty()) {
return new ArrayList<>();
}
try {
List<D> result = new ArrayList<>(sourceList.size());
for (S source : sourceList) {
result.add(OBJECT_MAPPER.convertValue(source, destinationClass));
}
return result;
} catch (Exception e) {
throw new RuntimeException("BeanMapper convertList failed: " +
"destination=" + destinationClass.getName(), e);
}
}
/**
* 深拷贝对象
*
* @param source 源对象
* @param <T> 类型
* @return 拷贝后的对象
*/
@SuppressWarnings("unchecked")
public <T> T copy(T source) {
if (source == null) {
return null;
}
try {
return (T) OBJECT_MAPPER.convertValue(source, source.getClass());
} catch (Exception e) {
throw new RuntimeException("BeanMapper copy failed: " +
"source=" + source.getClass().getName(), e);
}
}
}
\ No newline at end of file
package com.jomalls.custom.app.utils;
import com.jomalls.custom.app.exception.ServiceException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
/**
* @Author: Lizh
* @Date: 2026/5/28 11:58
* @Description:
* @Version: 1.0
*/
public class CustomAsserts {
public static void assertTrue(boolean expression, String message) {
if (!expression) {
throw new ServiceException(message);
}
}
public static <T> void nonNull(T t, String message) {
if (t instanceof String) {
assertTrue(StringUtils.isNotBlank((CharSequence)t), message);
} else if (t instanceof Collection) {
assertTrue(CollectionUtils.isNotEmpty((Collection<?>)t), message);
} else {
assertTrue(null != t, message);
}
}
}
package com.jomalls.custom.app.utils;
import com.jomalls.custom.app.constant.CodeEnum;
import lombok.Getter;
import lombok.Setter;
import java.io.Serial;
import java.io.Serializable;
/**
* @Author: Lizh
* @Date: 2026/5/28 10:29
* @Description: 统一返回结果类
* @Version: 1.0
*/
@Setter
@Getter
public class R<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private int code;
private String msg;
private T data;
public R() {
}
public R(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> R<T> ok() {
return new R<>(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMsg(), null);
}
public static <T> R<T> ok(T data) {
return new R<>(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getMsg(), data);
}
public static <T> R<T> ok(String msg, T data) {
return new R<>(CodeEnum.SUCCESS.getCode(), msg, data);
}
public static <T> R<T> fail(int code, String msg) {
return new R<>(code, msg, null);
}
public static <T> R<T> fail(CodeEnum codeEnum) {
return new R<>(codeEnum.getCode(), codeEnum.getMsg(), null);
}
public static <T> R<T> fail(String msg) {
return new R<>(CodeEnum.FAIL.getCode(), msg, null);
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>custom-server-core</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
</dependencies>
</project>
package com.jomalls.custom.mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
public interface BaseMapper <M> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<M> {
int insertBatchSomeColumn(@Param("list") Collection<M> collection);
}
\ No newline at end of file
package com.jomalls.custom.page;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @Author: Lizh
* @Date: 2026/5/28 11:12
* @Description: 分页查询
* @Version: 1.0
*/
@Data
public class PageRequest implements Pageable, Serializable {
@Serial
private static final long serialVersionUID = 8280485938848398236L;
private static final long DEFAULT_CURRENT = 1L;
private static final long DEFAULT_SIZE = 50L;
private long current;
private long size;
private boolean isSearchCount;
public PageRequest() {
this(DEFAULT_CURRENT, DEFAULT_SIZE);
}
public PageRequest(long current, long size) {
this.current = current > 0 ? current : DEFAULT_CURRENT;
this.size = size > 0 ? size : DEFAULT_SIZE;
this.isSearchCount = true;
}
/**
* 获取当前页码,默认值为 1
*/
public long getCurrent() {
return current > 0 ? current : DEFAULT_CURRENT;
}
/**
* 获取每页大小,默认值为 10
*/
public long getSize() {
return size > 0 ? size : DEFAULT_SIZE;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof PageRequest)) {
return false;
}
PageRequest other = (PageRequest) o;
if (!other.canEqual(this)) {
return false;
}
if (this.getCurrent() != other.getCurrent()) {
return false;
}
if (this.getSize() != other.getSize()) {
return false;
}
return this.isSearchCount() == other.isSearchCount();
}
protected boolean canEqual(Object other) {
return (other instanceof PageRequest);
}
public int hashCode() {
final int PRIME = 59;
int result = 1;
long current = this.getCurrent();
result = result * PRIME + Long.hashCode(current);
long size = this.getSize();
result = result * PRIME + Long.hashCode(size);
result = result * PRIME + Boolean.hashCode(this.isSearchCount());
return result;
}
public String toString() {
return "PageRequest(current=" + this.getCurrent() + ", size=" + this.getSize() + ", isSearchCount=" + this.isSearchCount() + ")";
}
}
\ No newline at end of file
package com.jomalls.custom.page;
/**
* @Author: Lizh
* @Date: 2026/5/28 11:11
* @Description: 基础接口
* @Version: 1.0
*/
public interface Pageable {
long getSize();
long getCurrent();
boolean isSearchCount();
}
package com.jomalls.custom.page;
import lombok.Data;
import java.io.Serial;
import java.util.Map;
/**
* @Author: Lizh
* @Date: 2026/5/28 11:16
* @Description:
* @Version: 1.0
*/
@Data
public class QueryCondition extends PageRequest {
@Serial
private static final long serialVersionUID = 5082098480613756697L;
/**
* 查询条件
*/
private Map<String, Object> condition;
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof QueryCondition)) {
return false;
}
QueryCondition other = (QueryCondition)o;
if (other.canEqual(this)) {
return false;
}
if (!super.equals(o)) {
return false;
}
Object thisCondition = this.getCondition();
Object otherCondition = other.getCondition();
if (thisCondition == null) {
if (otherCondition != null) {
return false;
}
}
if (thisCondition != null) {
return thisCondition.equals(otherCondition);
}
return true;
}
protected boolean canEqual(Object other) {
return !(other instanceof QueryCondition);
}
public int hashCode() {
int PRIME = 59;
int result = super.hashCode();
Object $condition = this.getCondition();
result = result * PRIME + ($condition == null ? 43 : $condition.hashCode());
return result;
}
public QueryCondition() {
}
public String toString() {
return "QueryCondition(condition=" + this.getCondition() + ")";
}
}
package com.jomalls.custom.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jomalls.custom.page.Pageable;
import com.jomalls.custom.page.QueryCondition;
import java.util.Collection;
/**
* @Author: Lizh
* @Date: 2026/5/28 10:50
* @Description: Domain层BaseService接口
* @Version: 1.0
*/
public interface IBaseService<M> extends IService<M> {
/**
* 按条件查询
*
* @param queryCondition 查询条件
* @return 实体集合
*/
Collection<M> select(QueryCondition queryCondition);
/**
* 按条件分页查询
*
* @param queryCondition 查询条件
* @return 分页结果
*/
IPage<M> selectPage(QueryCondition queryCondition);
/**
* 按条件分页查询
*
* @param wrapper 查询条件包装器
* @param pageable 分页参数
* @return 分页结果
*/
IPage<M> selectPage(Wrapper<M> wrapper, Pageable pageable);
/**
* 批量保存
*
* @param collection 实体集合
* @return 是否保存成功
*/
boolean saveBatch(Collection<M> collection);
}
\ No newline at end of file
package com.jomalls.custom.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jomalls.custom.mapper.BaseMapper;
import com.jomalls.custom.service.IBaseService;
import com.jomalls.custom.page.Pageable;
import com.jomalls.custom.page.QueryCondition;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
/**
* @Author: Lizh
* @Date: 2026/5/28 10:50
* @Description: Domain层BaseService实现类
* @Version: 1.0
*/
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements IBaseService<T> {
private final SqlSessionFactory sqlSessionFactory;
@Autowired
public BaseServiceImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
/**
* 按条件查询
*
* @param queryCondition 查询条件
* @return 实体集合
*/
@Override
public Collection<T> select(QueryCondition queryCondition) {
return this.list();
}
/**
* 按条件分页查询
*
* @param queryCondition 查询条件
* @return 分页结果
*/
@Override
public IPage<T> selectPage(QueryCondition queryCondition) {
Page<T> page = new Page<>(queryCondition.getCurrent(), queryCondition.getSize());
page.setSearchCount(queryCondition.isSearchCount());
return this.page(page);
}
/**
* 按条件分页查询
*
* @param wrapper 查询条件包装器
* @param pageable 分页参数
* @return 分页结果
*/
@Override
public IPage<T> selectPage(Wrapper<T> wrapper, Pageable pageable) {
Page<T> page = new Page<>(pageable.getCurrent(), pageable.getSize());
page.setSearchCount(pageable.isSearchCount());
return this.page(page, wrapper);
}
/**
* 批量保存
*
* @param collection 实体集合
* @return 是否保存成功
*/
@Override
public boolean saveBatch(Collection<T> collection) {
return this.baseMapper.insertBatchSomeColumn(collection) > 0;
}
@Override
public SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>custom-server-domain</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server-core</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.jomalls.custom.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户实体类
*/
@Getter
@Setter
@TableName("sys_user")
public class SysUserEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
// Getters and Setters
/**
* 用户ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
@TableField("username")
private String username;
/**
* 密码
*/
@TableField("password")
private String password;
/**
* 邮箱
*/
@TableField("email")
private String email;
/**
* 手机号
*/
@TableField("phone")
private String phone;
/**
* 状态:0-禁用,1-启用
*/
@TableField("status")
private Integer status;
/**
* 创建时间
*/
@TableField("created_at")
private LocalDateTime createdAt;
/**
* 更新时间
*/
@TableField("updated_at")
private LocalDateTime updatedAt;
public SysUserEntity() {
}
public SysUserEntity(String username, String password, String email, String phone) {
this.username = username;
this.password = password;
this.email = email;
this.phone = phone;
this.status = 1;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@Override
public String toString() {
return "SysUser{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}
\ No newline at end of file
package com.jomalls.custom.dal.mapper;
import com.jomalls.custom.dal.entity.SysUserEntity;
import com.jomalls.custom.mapper.BaseMapper;
/**
* 用户Mapper接口
*/
public interface SysUserMapper extends BaseMapper<SysUserEntity> {
}
\ No newline at end of file
package com.jomalls.custom.domain.service;
import com.jomalls.custom.dal.entity.SysUserEntity;
import com.jomalls.custom.service.IBaseService;
/**
* 用户领域服务接口
*/
public interface SysUserDomainService extends IBaseService<SysUserEntity> {
}
\ No newline at end of file
package com.jomalls.custom.domain.service.impl;
import com.jomalls.custom.dal.entity.SysUserEntity;
import com.jomalls.custom.dal.mapper.SysUserMapper;
import com.jomalls.custom.domain.service.SysUserDomainService;
import com.jomalls.custom.service.impl.BaseServiceImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 用户领域服务实现类
*/
@Service
public class SysUserDomainServiceImpl extends BaseServiceImpl<SysUserMapper, SysUserEntity> implements SysUserDomainService {
@Autowired
public SysUserDomainServiceImpl(SqlSessionFactory sqlSessionFactory) {
super(sqlSessionFactory);
}
// 用户 Mapper 接口,自定义方法或者基础方法重写
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jomalls.custom.dal.mapper.SysUserMapper">
<!-- 通用查询列 -->
<sql id="Base_Column_List">
id, username, password, email, phone, status, created_at, updated_at
</sql>
<!-- 根据用户名查询用户 -->
<select id="selectByUsername" parameterType="java.lang.String" resultType="com.jomalls.custom.dal.entity.SysUserEntity">
SELECT
<include refid="Base_Column_List"/>
FROM sys_user
WHERE username = #{username}
AND status = 1
</select>
<!-- 根据邮箱查询用户 -->
<select id="selectByEmail" parameterType="java.lang.String" resultType="com.jomalls.custom.dal.entity.SysUserEntity">
SELECT
<include refid="Base_Column_List"/>
FROM sys_user
WHERE email = #{email}
AND status = 1
</select>
<!-- 分页查询用户列表 -->
<select id="selectUserPage" parameterType="com.jomalls.custom.dal.entity.SysUserEntity" resultType="com.jomalls.custom.dal.entity.SysUserEntity">
SELECT
<include refid="Base_Column_List"/>
FROM sys_user
<where>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY created_at DESC
</select>
<!-- 查询所有启用状态的用户 -->
<select id="selectAllActiveUsers" resultType="com.jomalls.custom.dal.entity.SysUserEntity">
SELECT
<include refid="Base_Column_List"/>
FROM sys_user
WHERE status = 1
ORDER BY created_at DESC
</select>
</mapper>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>custom-server-integrate</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>custom-server-starter</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<docker.plugin.version>1.1.1</docker.plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server-webapp</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package com.jomalls.custom;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动类
*
* @author lizh
* @date 2026-05-26 18:25:27
*/
@SpringBootApplication
@EnableScheduling
public class CustomServerApplication {
public static void main(String[] args) {
SpringApplication.run(CustomServerApplication.class, args);
}
}
\ No newline at end of file
package com.jomalls.custom.config;
import com.jomalls.custom.app.exception.ServiceException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class CommonExceptionHandlerAdvice {
@ExceptionHandler(ServiceException.class)
public ResponseEntity<com.jomalls.custom.app.utils.R<Object>> handleServiceException(ServiceException e) {
return ResponseEntity.ok(com.jomalls.custom.app.utils.R.fail(e.getMessage()));
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<com.jomalls.custom.app.utils.R<Object>> handleRuntimeException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(com.jomalls.custom.app.utils.R.fail(e.getMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<com.jomalls.custom.app.utils.R<Object>> handleException(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(com.jomalls.custom.app.utils.R.fail(com.jomalls.custom.app.constant.CodeEnum.FAIL));
}
}
\ No newline at end of file
package com.jomalls.custom.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisInterceptor {
}
\ No newline at end of file
package com.jomalls.custom.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.jomalls.custom.dal.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean(name = "sqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
GlobalConfig globalConfig = new GlobalConfig();
GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
dbConfig.setIdType(IdType.AUTO);
globalConfig.setDbConfig(dbConfig);
globalConfig.setSqlInjector(new DefaultSqlInjector());
factory.setGlobalConfig(globalConfig);
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/**/*.xml");
factory.setMapperLocations(resources);
return factory.getObject();
}
@Bean(name = "sqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
\ No newline at end of file
package com.jomalls.custom.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: Lizh
* @Date: 2026/5/27 11:14
* @Description: Swagger配置
* @Version: 1.0
*/
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Custom Server API")
.version("1.0.0")
.description("Custom Server API 文档")
.contact(new Contact()
.name("Support Team")
.email("support@jomalls.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html")));
}
}
package com.jomalls.custom.config;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.resource.ResourceResolver;
import org.springframework.web.servlet.resource.ResourceResolverChain;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
public class PathPatternResourceResolver implements ResourceResolver {
@Override
public Resource resolveResource(HttpServletRequest request, String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
for (Resource location : locations) {
try {
Resource resource = location.createRelative(requestPath);
if (resource.exists() && !requestPath.contains("..")) {
return resource;
}
} catch (IOException e) {
}
}
return chain.resolveResource(request, requestPath, locations);
}
@Override
public String resolveUrlPath(String resourcePath, List<? extends Resource> locations, ResourceResolverChain chain) {
return chain.resolveUrlPath(resourcePath, locations);
}
}
\ No newline at end of file
package com.jomalls.custom.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jomalls.custom.app.constant.CodeEnum;
import org.jspecify.annotations.NonNull;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.util.AntPathMatcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ControllerAdvice
public class RestResponseBodyConfig implements ResponseBodyAdvice<Object> {
private volatile List<String> noWrapUrls;
private final AntPathMatcher matcher = new AntPathMatcher();
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supports(MethodParameter returnType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), RestController.class)
|| AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class)
|| returnType.hasMethodAnnotation(ResponseBody.class);
}
@Override
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType,
MediaType selectedContentType,
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
@NonNull ServerHttpRequest request, ServerHttpResponse response) {
String json = "application/json";
String text = "text/plain";
if (!selectedContentType.toString().toLowerCase().contains(json)
&& !selectedContentType.toString().toLowerCase().contains(text)) {
return body;
} else {
String requestPath = request.getURI().getPath();
ServletServerHttpResponse httpResponse = (ServletServerHttpResponse) response;
if (httpResponse.getServletResponse().getStatus() == HttpStatus.NOT_FOUND.value()) {
return com.jomalls.custom.app.utils.R.fail(CodeEnum.NOT_FOUND);
} else if (this.isNoWrapResponseUrl(requestPath)) {
return body;
} else {
if (body instanceof String) {
try {
return objectMapper.writeValueAsString(com.jomalls.custom.app.utils.R.ok(body));
} catch (Exception e) {
return body;
}
}
return body instanceof com.jomalls.custom.app.utils.R ? body : com.jomalls.custom.app.utils.R.ok(body);
}
}
}
private boolean isNoWrapResponseUrl(String requestUrl) {
this.initNoWrapUrls();
for (String url : this.noWrapUrls) {
if (this.matcher.match(url, requestUrl)) {
return true;
}
}
return false;
}
private synchronized void initNoWrapUrls() {
if (this.noWrapUrls == null || this.noWrapUrls.isEmpty()) {
this.noWrapUrls = new ArrayList<>();
this.noWrapUrls.addAll(Arrays.asList("/**/swagger*/**", "/**/api-docs", "/health/check", "/swagger-resources"));
}
}
}
\ No newline at end of file
package com.jomalls.custom.config;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class SecurityInterceptor implements HandlerInterceptor {
}
\ No newline at end of file
package com.jomalls.custom.config;
import org.springframework.stereotype.Component;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class ThreadFactoryHandle implements java.util.concurrent.ThreadFactory {
private final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadFactory defaultThreadFactory = java.util.concurrent.Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
Thread thread = defaultThreadFactory.newThread(r);
thread.setName("custom-server-" + poolNumber.getAndIncrement() + "-thread");
return thread;
}
}
\ No newline at end of file
package com.jomalls.custom.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ThreadPoolAutoConfiguration {
}
\ No newline at end of file
package com.jomalls.custom.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolExecutorConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("custom-server-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
\ No newline at end of file
package com.jomalls.custom.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebConfig {
}
\ No newline at end of file
package com.jomalls.custom.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Bean
public SecurityInterceptor securityInterceptor() {
return new SecurityInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(securityInterceptor())
.excludePathPatterns("/swagger-ui/**", "/swagger-ui.html", "/doc.html", "/api-docs/**",
"/webjars/**", "/swagger-resources/**", "/sys/Serf/Health/*", "/error",
"/actuator/health", "/health/check");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH")
.allowCredentials(true)
.maxAge(3600)
.allowedHeaders("*");
}
}
\ No newline at end of file
## 数据库连接配置
spring.datasource.url=jdbc:mysql://172.16.19.99:3306/foxpsd_lizh?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=joshine
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
## Hikari连接池配置
#最小空闲连接数
spring.datasource.hikari.minimum-idle=5
#最大连接池大小
spring.datasource.hikari.maximum-pool-size=20
#连接超时时间(60秒)
spring.datasource.hikari.connection-timeout=60000
#空闲连接超时时间(600秒)
spring.datasource.hikari.idle-timeout=600000
#最大连接生命周期(900秒)
spring.datasource.hikari.max-lifetime=900000
#验证超时时间(3000秒)
spring.datasource.hikari.validation-timeout=3000
## MyBatis-Plus SQL日志(可选开启)
#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
\ No newline at end of file
## 数据库连接配置
spring.datasource.url=jdbc:mysql://172.16.19.99:3306/foxpsd_lizh?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=joshine
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
## Hikari连接池配置
#最小空闲连接数
spring.datasource.hikari.minimum-idle=5
#最大连接池大小
spring.datasource.hikari.maximum-pool-size=20
#连接超时时间(60秒)
spring.datasource.hikari.connection-timeout=60000
#空闲连接超时时间(600秒)
spring.datasource.hikari.idle-timeout=600000
#最大连接生命周期(900秒)
spring.datasource.hikari.max-lifetime=900000
#验证超时时间(3000秒)
spring.datasource.hikari.validation-timeout=3000
## MyBatis-Plus SQL日志(可选开启)
#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
\ No newline at end of file
## 服务器配置
server.port=40071
server.servlet.context-path=/
## Tomcat配置
server.tomcat.uri-encoding=UTF-8
server.tomcat.accept-count=1000
server.tomcat.threads.max=800
server.tomcat.threads.min-spare=100
## Spring配置
spring.application.name=custom-server
spring.profiles.active=datasource
spring.main.allow-circular-references=true
## Jackson配置
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
## SpringDoc OpenAPI配置
springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.path=/swagger-ui.html
## MyBatis-Plus配置
mybatis-plus.mapper-locations=classpath*:mapper/**/*.xml
mybatis-plus.type-aliases-package=com.jomalls.custom.domain.entity
mybatis-plus.configuration.call-setters-on-nulls=true
## 数据版本控制
data.version.control.switch=false
default.scp.data.version=1.0
## 时区配置
TZ=Asia/Shanghai
\ No newline at end of file
## Spring MVC配置
spring:
application:
name: custom-server
profiles:
active: datasource
mvc:
pathmatch:
matching-strategy: ant_path_matcher
## 日志配置
logging:
level:
com.jomalls.custom: DEBUG
org.mybatis: DEBUG
org.springframework.web: DEBUG
## MyBatis-Plus配置
mybatis-plus:
configuration:
# 是否将SQL打印到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
mapper-locations: classpath*:mapper/**/*.xml
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/root/custom-v2/logs"/>
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用户访问日志输出 -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.jomalls.custom" level="info"/>
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn"/>
<root level="info">
<appender-ref ref="console"/>
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info"/>
<appender-ref ref="file_error"/>
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
</configuration>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>custom-server-webapp</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server-app</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server-domain</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>hutool-core</artifactId>
<groupId>cn.hutool</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
package com.jomalls.custom.webapp.controller;
import com.jomalls.custom.app.constant.Constants;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: Lizh
* @Date: 2026/5/28 09:27
* @Description: 心跳检测接口
* @Version: 1.0
*/
public class HealthController {
/**
* 健康状态字段
*/
private static final String HEALTH_STATUS_KEY = "status";
/**
* 健康状态值
*/
private static final String HEALTH_STATUS_UP = "UP";
/**
* 健康检查接口,返回UP状态
*/
@Operation(summary = "健康检测接口", description = "健康检测接口")
@GetMapping(value = "/actuator/health")
public Map<String, String> check() {
Map<String, String> map = new HashMap<>(Constants.DEFAULT_INITIAL_CAPACITY);
map.put(HEALTH_STATUS_KEY, HEALTH_STATUS_UP);
return map;
}
}
package com.jomalls.custom.webapp.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jomalls.custom.app.model.SysUserPageVO;
import com.jomalls.custom.app.model.SysUserVO;
import com.jomalls.custom.app.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 用户管理控制器
*/
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户管理相关API")
public class SysUserController {
private final SysUserService sysUserService;
@Autowired
public SysUserController(SysUserService sysUserService) {
this.sysUserService = sysUserService;
}
/**
* 列表查询接口
*
* @param sysUser 条件model
* @return list集合
*/
@Operation(summary = "列表查询接口", description = "根据条件查询列表接口(不分页)")
@RequestMapping(value = "/queryList", method = RequestMethod.POST)
public List<SysUserVO> list(@RequestBody SysUserVO sysUser) {
return sysUserService.list(sysUser);
}
/**
* 根据条件查询分页列表接口
*
* @param sysUser 分页入参model
* @return 分页对象
*/
@Operation(summary = "分页列表接口", description = "根据条件查询分页列表接口")
@RequestMapping(value = "/pageList", method = RequestMethod.POST)
public IPage<SysUserVO> pageList(@RequestBody SysUserPageVO sysUserPage) {
return sysUserService.pageList(sysUserPage);
}
/**
* 根据主键id查询详情
*
* @param id 主键
* @return 实体model
*/
@Operation(summary = "根据主键id查询详情", description = "根据主键id查询详情")
@RequestMapping(value = "/info/{id}", method = RequestMethod.GET)
public SysUserVO getUserById(@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
return sysUserService.info(id);
}
/**
* 保存对象
*
* @param sysUser 保存对象
*/
@Operation(summary = "保存对象", description = "保存对象")
@RequestMapping(value = "/save", method = RequestMethod.POST)
public void save(@RequestBody @Valid SysUserVO sysUser) {
sysUserService.save(sysUser);
}
/**
* 根据id修改对象
*
* @param sysUser 修改对象
*/
@Operation(summary = "根据id修改对象", description = "根据id修改对象")
@RequestMapping(value = "/updateById", method = RequestMethod.PUT)
public void updateById(@RequestBody SysUserVO sysUser) {
sysUserService.updateById(sysUser);
}
/**
* 根据主键id进行删除
*
* @param id 主键
*/
@Operation(summary = "根据id删除对象", description = "根据id删除对象")
@RequestMapping(value = "/deleteById/{id}", method = RequestMethod.DELETE)
public void deleteById(@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
sysUserService.deleteById(id);
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.5</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.jomalls.custom</groupId>
<artifactId>custom-server</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>21</java.version>
<commons-lang3.version>3.14.0</commons-lang3.version>
<commons-io.version>2.15.1</commons-io.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<log4j.version>2.23.1</log4j.version>
<mybatis-plus.version>3.5.8</mybatis-plus.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-metadata</artifactId>
<version>3.0.0</version>
</dependency>
<!-- SpringDoc OpenAPI for Spring Boot 4.x -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!-- Orika Bean Mapper -->
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>custom-server-core</module>
<module>custom-server-webapp</module>
<module>custom-server-app</module>
<module>custom-server-domain</module>
<module>custom-server-integrate</module>
<module>custom-server-starter</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment