This commit is contained in:
wucongxing8150 2025-08-11 10:39:53 +08:00
commit b377c87a1a
456 changed files with 28628 additions and 0 deletions

47
sa-admin/pom.xml Normal file
View File

@ -0,0 +1,47 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.lab1024</groupId>
<artifactId>sa-parent</artifactId>
<version>3.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sa-admin</artifactId>
<version>3.0.0</version>
<packaging>jar</packaging>
<name>sa-admin</name>
<description>sa-admin project</description>
<dependencies>
<dependency>
<groupId>net.lab1024</groupId>
<artifactId>sa-base</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>net.lab1024.sa.admin.AdminApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,40 @@
package net.lab1024.sa.admin;
import net.lab1024.sa.base.listener.Ip2RegionListener;
import net.lab1024.sa.base.listener.LogVariableListener;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* SmartAdmin 项目启动类
*
* @Author 1024创新实验室-主任:卓大
* @Date 2022-08-29 21:00:58
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@EnableCaching
@EnableScheduling
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@ComponentScan(AdminApplication.COMPONENT_SCAN)
@MapperScan(value = AdminApplication.COMPONENT_SCAN, annotationClass = Mapper.class)
@SpringBootApplication(exclude = {UserDetailsServiceAutoConfiguration.class})
public class AdminApplication {
public static final String COMPONENT_SCAN = "net.lab1024.sa";
public static void main(String[] args) {
SpringApplication application = new SpringApplication(AdminApplication.class);
// 添加 日志监听器使 log4j2-spring.xml 可以间接读取到配置文件的属性
application.addListeners(new LogVariableListener(), new Ip2RegionListener());
application.run(args);
}
}

View File

@ -0,0 +1,28 @@
package net.lab1024.sa.admin.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component
public class AppConfig {
@Value("${app.apiUrl}")
private String apiUrl;
@Value("${app.secretKey}")
private String secretKey;
@Value("${app.imagePrefix}")
private String imagePrefix;
@Value("${app.platform}")
private String platform;
@Value("${app.platformPointAccount}")
private String platformPointAccount;
@Value("${app.access-token}")
private String accessToken;
}

View File

@ -0,0 +1,16 @@
package net.lab1024.sa.admin.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component
public class DySmsConfig {
@Value("${aliyun.sms.access-key:}")
private String accessKey;
@Value("${aliyun.sms.access-secret:}")
private String accessSecret;
}

View File

@ -0,0 +1,13 @@
package net.lab1024.sa.admin.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component
public class EnvConfig {
@Value("${spring.profiles.active:prod}")
private String active;
}

View File

@ -0,0 +1,41 @@
package net.lab1024.sa.admin.config;
import jakarta.annotation.Resource;
import net.lab1024.sa.admin.interceptor.AdminInterceptor;
import net.lab1024.sa.base.config.SwaggerConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* web相关配置
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private AdminInterceptor adminInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminInterceptor)
.excludePathPatterns(SwaggerConfig.SWAGGER_WHITELIST)
.addPathPatterns("/**");
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}

View File

@ -0,0 +1,25 @@
package net.lab1024.sa.admin.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneId;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
private static final ZoneId BEIJING_ZONE = ZoneId.of("Asia/Shanghai");
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createdAt", LocalDateTime.class, LocalDateTime.now(BEIJING_ZONE));
this.strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now(BEIJING_ZONE));
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
}
}

View File

@ -0,0 +1,28 @@
package net.lab1024.sa.admin.config;
import net.lab1024.sa.base.module.support.operatelog.core.OperateLogAspect;
import net.lab1024.sa.base.module.support.operatelog.core.OperateLogConfig;
import org.springframework.context.annotation.Configuration;
/**
* 操作日志切面 配置
*
* @Author 1024创新实验室: 罗伊
* @Date 2022-05-30 21:22:12
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Configuration
public class OperateLogAspectConfig extends OperateLogAspect{
/**
* 配置信息
*/
@Override
public OperateLogConfig getOperateLogConfig() {
return OperateLogConfig.builder().corePoolSize(1).queueCapacity(10000).build();
}
}

View File

@ -0,0 +1,25 @@
package net.lab1024.sa.admin.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component
public class OssConfig {
@Value("${file.storage.cloud.endpoint}")
private String endpoint;
@Value("${file.storage.cloud.access-key}")
private String accessKey;
@Value("${file.storage.cloud.secret-key}")
private String accessKeySecret;
@Value("${file.storage.cloud.bucket-name}")
private String bucket;
@Value("${file.storage.cloud.url-prefix}")
private String customDomainName;
}

View File

@ -0,0 +1,73 @@
package net.lab1024.sa.admin.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@RequiredArgsConstructor
public class RedisConfiguration {
// 开发环境Redis配置
@Value("${spring.data.redis.database:0}")
private String database;
@Value("${spring.data.redis.host:localhost}")
private String host;
@Value("${spring.data.redis.port:6379}")
private Integer port;
@Value("${spring.data.redis.password:}")
private String password;
// 生产环境Redis配置如果没有配置使用开发环境配置
@Value("${spring.data.prod-redis.host:${spring.data.redis.host:localhost}}")
private String prodHost;
@Value("${spring.data.prod-redis.port:${spring.data.redis.port:6379}}")
private Integer prodPort;
@Value("${spring.data.prod-redis.database:${spring.data.redis.database:0}}")
private String prodDatabase;
@Value("${spring.data.prod-redis.password:${spring.data.redis.password:}}")
private String prodPassword;
// 移除redisTemplate的定义使用框架自带的
@Bean
@Qualifier("prodRedisTemplate")
public RedisTemplate<String, Object> prodRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(prodHost);
configuration.setPort(prodPort);
configuration.setDatabase(Integer.parseInt(prodDatabase));
if (prodPassword != null && !prodPassword.isEmpty()) {
configuration.setPassword(RedisPassword.of(prodPassword));
}
LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration);
factory.afterPropertiesSet();
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用String序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
return template;
}
}

View File

@ -0,0 +1,16 @@
package net.lab1024.sa.admin.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Data
@Component
public class WxMaConfig {
@Value("${wechat.miniapp.appid}")
private String appid;
@Value("${wechat.miniapp.secret}")
private String secret;
}

View File

@ -0,0 +1,68 @@
package net.lab1024.sa.admin.constant;
import net.lab1024.sa.base.constant.CacheKeyConst;
/**
* 缓存 key
*
* @Author 1024创新实验室-主任:卓大
* @Date 2022-01-07 18:59:22
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class AdminCacheConst extends CacheKeyConst {
public static class Department {
/**
* 部门列表
*/
public static final String DEPARTMENT_LIST_CACHE = "department_list_cache";
/**
* 部门树
*/
public static final String DEPARTMENT_TREE_CACHE = "department_tree_cache";
/**
* 某个部门以及下级的id列表
*/
public static final String DEPARTMENT_SELF_CHILDREN_CACHE = "department_self_children_cache";
/**
* 部门路径 缓存
*/
public static final String DEPARTMENT_PATH_CACHE = "department_path_cache";
}
/**
* 分类相关缓存
*/
public static class Category {
public static final String CATEGORY_ENTITY = "category_cache";
public static final String CATEGORY_SUB = "category_sub_cache";
public static final String CATEGORY_TREE = "category_tree_cache";
}
/**
* 登录相关
*/
public static class Login {
/**
* 请求用户信息
*/
public static final String REQUEST_EMPLOYEE = "login_request_employee";
/**
* 请求用户信息权限
*/
public static final String USER_PERMISSION = "login_user_permission";
}
}

View File

@ -0,0 +1,17 @@
package net.lab1024.sa.admin.constant;
import net.lab1024.sa.base.constant.RedisKeyConst;
/**
* redis key 常量类
*
* @Author 1024创新实验室-主任:卓大
* @Date 2022-01-07 18:59:22
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class AdminRedisKeyConst extends RedisKeyConst {
}

View File

@ -0,0 +1,59 @@
package net.lab1024.sa.admin.constant;
import net.lab1024.sa.base.constant.SwaggerTagConst;
/**
* swagger
*
* @Author 1024创新实验室:罗伊
* @Date 2022-01-07 18:59:22
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class AdminSwaggerTagConst extends SwaggerTagConst {
public static class Business {
public static final String MANAGER_CATEGORY = "ERP进销存-分类管理";
public static final String MANAGER_GOODS = "ERP进销存-商品管理";
public static final String OA_BANK = "OA办公-银行卡信息";
public static final String OA_ENTERPRISE = "OA办公-企业";
public static final String OA_INVOICE = "OA办公-发票信息";
public static final String OA_NOTICE = "OA办公-通知公告";
}
public static class System {
public static final String SYSTEM_LOGIN = "系统-员工登录";
public static final String SYSTEM_EMPLOYEE = "系统-员工管理";
public static final String SYSTEM_DEPARTMENT = "系统-部门管理";
public static final String SYSTEM_MENU = "系统-菜单";
public static final String SYSTEM_DATA_SCOPE = "系统-系统-数据范围";
public static final String SYSTEM_ROLE = "系统-角色";
public static final String SYSTEM_ROLE_DATA_SCOPE = "系统-角色-数据范围";
public static final String SYSTEM_ROLE_EMPLOYEE = "系统-角色-员工";
public static final String SYSTEM_ROLE_MENU = "系统-角色-菜单";
public static final String SYSTEM_POSITION = "系统-职务管理";
public static final String SYSTEM_MESSAGE = "系统-消息";
}
}

View File

@ -0,0 +1,77 @@
package net.lab1024.sa.admin.extend.aliyun;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import net.lab1024.sa.admin.config.DySmsConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
@Slf4j
@Component
public class DySms {
@Resource
private DySmsConfig dySmsConfig;
private static final String endpoint = "dysmsapi.aliyuncs.com";
private static final String signName = "肝胆相照";
/**
* 创建短信客户端
*/
private Client createClient() throws Exception {
Config config = new Config()
.setAccessKeyId(dySmsConfig.getAccessKey())
.setAccessKeySecret(dySmsConfig.getAccessSecret())
.setEndpoint(endpoint);
return new Client(config);
}
/**
* 发送短信
*
* @param phoneNumber 手机号
* @param templateCode 模板CODE
* @param sceneDesc 场景说明用于日志或调试
* @param templateParam 模板参数
*/
public void sendSms(String phoneNumber, String templateCode, String sceneDesc, Map<String, Object> templateParam) {
try {
Client client = createClient();
ObjectMapper objectMapper = new ObjectMapper();
String paramJson = objectMapper.writeValueAsString(templateParam);
SendSmsRequest request = new SendSmsRequest()
.setPhoneNumbers(phoneNumber)
.setSignName(signName)
.setTemplateCode(templateCode)
.setTemplateParam(paramJson);
// 只用标准的RuntimeOptions不加任何自定义字段
RuntimeOptions runtime = new RuntimeOptions();
SendSmsResponse response = client.sendSmsWithOptions(request, runtime);
if (response.getBody() == null || !"OK".equals(response.getBody().getCode())) {
log.error("短信发送失败,手机号:{},场景:{},返回信息:{}", phoneNumber, sceneDesc, response.getBody() != null ? response.getBody().getMessage() : "无返回体");
throw new RuntimeException("短信发送失败: " + (response.getBody() != null ? response.getBody().getMessage() : "无返回体"));
} else {
log.info("短信发送成功,手机号:{},场景:{},返回信息:{}", phoneNumber, sceneDesc, response.getBody().getMessage());
}
} catch (TeaException e) {
log.error("阿里云短信发送异常,手机号:{},场景:{},错误信息:{}", phoneNumber, sceneDesc, e.getMessage(), e);
throw new RuntimeException("阿里云短信发送异常", e);
} catch (Exception e) {
log.error("短信发送异常,手机号:{},场景:{},错误信息:{}", phoneNumber, sceneDesc, e.getMessage(), e);
throw new RuntimeException("短信发送异常", e);
}
}
}

View File

@ -0,0 +1 @@
// 此文件已移动到 config 目录请使用 net.lab1024.sa.admin.config.DySmsConfig

View File

@ -0,0 +1,116 @@
package net.lab1024.sa.admin.extend.aliyun;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.admin.config.OssConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Slf4j
@Component
public class Oss {
private static OssConfig ossConfig;
private static final Logger logger = LoggerFactory.getLogger(Oss.class);
public Oss(OssConfig ossConfig) {
Oss.ossConfig = ossConfig;
}
public static OSS createClient() {
return new OSSClientBuilder().build(ossConfig.getEndpoint(), ossConfig.getAccessKey(), ossConfig.getAccessKeySecret());
}
public static boolean putObject(String fileName, byte[] content) {
if (content == null || content.length == 0) {
logger.warn("Failed to upload object to OSS: content is null or empty, fileName={}", fileName);
return false;
}
if (fileName == null || fileName.trim().isEmpty()) {
logger.warn("Failed to upload object to OSS: fileName is null or empty");
return false;
}
OSS client = createClient();
ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
try {
// 推断 Content-Type
String contentType = determineContentType(fileName);
// 创建对象元数据
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(content.length);
metadata.setContentType(contentType);
// 执行上传
PutObjectRequest request = new PutObjectRequest(ossConfig.getBucket(), fileName, inputStream, metadata);
client.putObject(request);
logger.info("Successfully uploaded object to OSS: bucket={}, fileName={}, size={} bytes",
ossConfig.getBucket(), fileName, content.length);
return true;
} catch (Exception e) {
logger.error("Failed to upload object to OSS. fileName={}, error={}", fileName, e.getMessage(), e);
return false;
} finally {
try {
inputStream.close(); // 可以不关ByteArrayInputStream 空操作但建议写上
} catch (IOException ignored) { }
client.shutdown();
}
}
private static String determineContentType(String fileName) {
if (fileName == null) return "application/octet-stream";
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex < 0) {
return "application/octet-stream";
}
String ext = fileName.substring(lastDotIndex + 1).toLowerCase();
return switch (ext) {
case "jpg", "jpeg" -> "image/jpeg";
case "png" -> "image/png";
case "gif" -> "image/gif";
case "bmp" -> "image/bmp";
case "webp" -> "image/webp";
case "pdf" -> "application/pdf";
case "txt" -> "text/plain";
case "html", "htm" -> "text/html";
case "xml" -> "application/xml";
case "json" -> "application/json";
case "mp4" -> "video/mp4";
case "avi" -> "video/x-msvideo";
case "mp3" -> "audio/mpeg";
default -> "application/octet-stream";
};
}
// 下载文件为 byte[]
public static byte[] getObjectToByte(String fileName) {
OSS client = createClient();
try (OSSObject ossObject = client.getObject(ossConfig.getBucket(), fileName);
InputStream content = ossObject.getObjectContent()) {
return content.readAllBytes();
} catch (Exception e) {
log.error("OSS 下载失败: fileName={}, error={}", fileName, e.getMessage(), e);
return null;
} finally {
client.shutdown();
}
}
}

View File

@ -0,0 +1 @@
// 此文件已移动到 config 目录请使用 net.lab1024.sa.admin.config.OssConfig

View File

@ -0,0 +1 @@
// 此文件已移动到 config 目录请使用 net.lab1024.sa.admin.config.AppConfig

View File

@ -0,0 +1,164 @@
package net.lab1024.sa.admin.extend.app;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import net.lab1024.sa.base.common.exception.BusinessException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@Slf4j
public class Base {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 生成签名
* @param params 需要签名的参数支持嵌套 Map
* @param secretKey 密钥
* @return 签名字符串
*/
public static String genSignature(Map<String, Object> params, String secretKey) {
try {
// Step 1: Map 进行递归排序
Map<String, Object> sortedMap = sortMapRecursively(params);
// Step 2: 转为 JSON 字符串
String json = objectMapper.writeValueAsString(sortedMap);
// Step 3: 使用 HMAC-SHA256 签名
return hmacSha256(json, secretKey);
} catch (JsonProcessingException e) {
return null;
}
}
/**
* Map<String, Object> 进行递归排序key 排序支持嵌套 Map List
*/
public static Map<String, Object> sortMapRecursively(Map<String, Object> data) {
Map<String, Object> sortedMap = new TreeMap<>(); // TreeMap 会自动按 key 排序
for (Map.Entry<String, Object> entry : data.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Map) {
// 如果是嵌套 map递归排序
@SuppressWarnings("unchecked")
Map<String, Object> nestedMap = (Map<String, Object>) value;
sortedMap.put(key, sortMapRecursively(nestedMap));
} else if (value instanceof List) {
// 如果是列表检查每一项是否是 map如果是则排序
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) value;
List<Object> sortedList = new ArrayList<>();
for (Object item : list) {
if (item instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> nestedItemMap = (Map<String, Object>) item;
sortedList.add(sortMapRecursively(nestedItemMap));
} else {
sortedList.add(item);
}
}
sortedMap.put(key, sortedList);
} else {
// 普通字段直接插入
sortedMap.put(key, value);
}
}
return sortedMap;
}
/**
* HMAC-SHA256 加密
*/
private static String hmacSha256(String data, String key) {
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256_HMAC.init(secretKeySpec);
byte[] result = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 转为 16 进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : result) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
return null;
}
}
/**
* 发送 POST JSON 请求
* @param url 请求地址
* @param jsonData JSON 字符串
* @param headers 请求头
* @return 响应字符串
*/
public String postJson(String url, String jsonData, Map<String, String> headers) {
HttpRequest request = HttpRequest.post(url)
.body(jsonData)
.header("Content-Type", "application/json");
if (headers != null) {
headers.forEach(request::header);
}
try (HttpResponse response = request.execute()) {
return response.body();
}
}
/**
* 校验签名使用已反序列化的 dto 对象
* @param request HttpServletRequest用于获取 appId sign
* @param secretKey 签名用的密钥
* @param dto 请求对象 addClinicalVideoApp
* @param mapper ObjectMapper 实例用于序列化 dto
*/
public void checkSign(HttpServletRequest request, String secretKey, Object dto, ObjectMapper mapper) {
try {
String appId = request.getHeader("appId");
String sign = request.getHeader("sign");
if (appId == null || appId.isEmpty()) {
throw new BusinessException("请求未授权(缺少 appId");
}
if (sign == null || sign.isEmpty()) {
throw new BusinessException("缺少签名");
}
// 转换 dto Map<String, Object> 用于签名生成
@SuppressWarnings("unchecked")
Map<String, Object> params = mapper.convertValue(dto, Map.class);
// 生成签名
String serverSign = genSignature(params, secretKey);
System.out.println("客户端签名: " + sign);
System.out.println("服务端签名: " + serverSign);
if (!sign.equals(serverSign)) {
throw new BusinessException("签名错误");
}
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
throw new BusinessException("签名校验失败:" + e.getMessage());
}
}
}

View File

@ -0,0 +1,48 @@
package net.lab1024.sa.admin.extend.app.Hospital;
import lombok.Data;
@Data
public class GetHospitalByUuidResponse
{
/** 接口调用状态。200正常其它值调用出错 */
private int code;
/** 结果说明。如果接口调用出错,那么返回错误描述。成功则返回 ok */
private String msg;
/** 接口返回的用户信息数据 */
private GetHospitalByUuidData data;
/** 接口是否调用成功 */
private boolean success;
/** 错误信息或提示信息 */
private String message;
/**
* - 详细数据
*/
@Data
public static class GetHospitalByUuidData {
/** 医院唯一标识 */
private String uuid;
/** 科室 */
private String name;
/** 等级 */
private String level;
/** 省份 */
private String prov_name;
/** 城市 */
private String city_name;
/** 医生数量 */
private Integer expert_num;
}
}

View File

@ -0,0 +1,68 @@
package net.lab1024.sa.admin.extend.app.Hospital;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import net.lab1024.sa.admin.config.AppConfig;
import net.lab1024.sa.base.common.exception.BusinessException;
import net.lab1024.sa.admin.extend.app.Base;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class Hospital extends Base {
@Resource
private AppConfig appConfig;
// 根据医院唯一标识获取医院数据
public GetHospitalByUuidResponse getHospitalByUuid(String hospitalIden) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("hospital_uuid", hospitalIden);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/getHospitalByUuid";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
GetHospitalByUuidResponse result = JSONUtil.toBean(response.body(), GetHospitalByUuidResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
if (result.getData() == null){
throw new BusinessException("失败");
}
return result;
}
}
}

View File

@ -0,0 +1,66 @@
package net.lab1024.sa.admin.extend.app.Reward;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import net.lab1024.sa.admin.config.AppConfig;
import net.lab1024.sa.base.common.exception.BusinessException;
import net.lab1024.sa.admin.extend.app.Base;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class Reward extends Base {
@Resource
private AppConfig appConfig;
// 打赏积分
public RewardResponse RewardPoint(String userUuid,Integer point,String authorUuid) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("user_uuid", userUuid);
requestData.put("bonusPoints", point);
requestData.put("recipients", authorUuid);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/admireBonusPoints";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
RewardResponse result = JSONUtil.toBean(response.body(), RewardResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
return result;
}
}
}

View File

@ -0,0 +1,20 @@
package net.lab1024.sa.admin.extend.app.Reward;
import lombok.Data;
@Data
public class RewardResponse
{
/** 接口调用状态。200正常其它值调用出错 */
private int code;
/** 结果说明。如果接口调用出错,那么返回错误描述。成功则返回 ok */
private String msg;
/** 接口是否调用成功 */
private boolean success;
/** 错误信息或提示信息 */
private String message;
}

View File

@ -0,0 +1,22 @@
package net.lab1024.sa.admin.extend.app.Score;
import lombok.Data;
@Data
public class ReportUserScoreResponse
{
/** 接口调用状态。200正常其它值调用出错 */
private int code;
/** 结果说明。如果接口调用出错,那么返回错误描述。成功则返回 ok */
private String msg;
/** 接口是否调用成功 */
private boolean success;
/** 错误信息或提示信息 */
private String message;
/** 接口返回的数据 */
private Integer data;
}

View File

@ -0,0 +1,66 @@
package net.lab1024.sa.admin.extend.app.Score;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import net.lab1024.sa.admin.config.AppConfig;
import net.lab1024.sa.base.common.exception.BusinessException;
import net.lab1024.sa.admin.extend.app.Base;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class Score extends Base {
@Resource
private AppConfig appConfig;
// 上报用户积分
public ReportUserScoreResponse ReportUserScore(String uuid,Integer bonuspoints,String bonuspointsNote) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("bonuspoints_note", bonuspointsNote);
requestData.put("bonuspoints", bonuspoints);
requestData.put("user_uuid", uuid);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/addBonusPoints";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
ReportUserScoreResponse result = JSONUtil.toBean(response.body(), ReportUserScoreResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
return result;
}
}
}

View File

@ -0,0 +1,56 @@
package net.lab1024.sa.admin.extend.app.UserInfo;
import lombok.Data;
@Data
public class GetUserInfoResponse {
/** 接口调用状态。200正常其它值调用出错 */
private int code;
/** 结果说明。如果接口调用出错,那么返回错误描述。成功则返回 ok */
private String msg;
/** 接口返回的用户信息数据 */
private ResponsData data;
/** 接口是否调用成功 */
private boolean success;
/** 错误信息或提示信息 */
private String message;
@Data
public static class ResponsData {
/** app唯一标识 */
private String uuid;
/** 科室 */
private String officeName;
/** 姓名 */
private String realname;
/** 医院唯一标识 */
private String hospitalUuid;
/** 手机号 */
private String mobile;
/** 头像地址 */
private String img_host;
/** 头像地址 */
private String photo;
/** 创建时间 */
private String createDate;
/** 职称 */
private String positionName;
/** 省份 */
private String provName;
}
}

View File

@ -0,0 +1,206 @@
package net.lab1024.sa.admin.extend.app.UserInfo;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import net.lab1024.sa.admin.extend.app.Base;
import net.lab1024.sa.admin.config.AppConfig;
import net.lab1024.sa.base.common.exception.BusinessException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class UserInfo extends Base {
@Resource
private AppConfig appConfig;
// 根据手机号获取信息V3
public GetUserInfoResponse getUserInfoByMobile(String mobile) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("mobile", mobile);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/getInfoByMobileV3";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
GetUserInfoResponse result = JSONUtil.toBean(response.body(), GetUserInfoResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
if (result.getData() == null){
throw new BusinessException("失败");
}
return result;
}
}
// 根据token获取信息V3
public GetUserInfoResponse getUserInfoByToken(String appToken) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("token", appToken);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/getInfoByToken";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
GetUserInfoResponse result = JSONUtil.toBean(response.body(), GetUserInfoResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
if (result.getData() == null){
throw new BusinessException("失败");
}
return result;
}
}
// 根据唯一标识获取信息V3
public GetUserInfoResponse getUserInfoByUuid(String uuid) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("user_uuid", uuid);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/getInfoByUuid";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
GetUserInfoResponse result = JSONUtil.toBean(response.body(), GetUserInfoResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
if (result.getData() == null){
throw new BusinessException("失败");
}
return result;
}
}
// 根据姓名获取信息
public GetUserInfoResponse getUserInfoByName(String doctorName) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("user_name", doctorName);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/getInfoByName";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
GetUserInfoResponse result = JSONUtil.toBean(response.body(), GetUserInfoResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
if (result.getData() == null){
throw new BusinessException("失败");
}
return result;
}
}
}

View File

@ -0,0 +1,64 @@
package net.lab1024.sa.admin.extend.app.UserPoint;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import net.lab1024.sa.admin.config.AppConfig;
import net.lab1024.sa.base.common.exception.BusinessException;
import net.lab1024.sa.admin.extend.app.Base;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class UserPoint extends Base {
@Resource
private AppConfig appConfig;
// 获取用户积分
public UserPointResponse GetUserPoint(String uuid) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("user_uuid", uuid);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/getBonusPoints";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
UserPointResponse result = JSONUtil.toBean(response.body(), UserPointResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
return result;
}
}
}

View File

@ -0,0 +1,22 @@
package net.lab1024.sa.admin.extend.app.UserPoint;
import lombok.Data;
@Data
public class UserPointResponse
{
/** 接口调用状态。200正常其它值调用出错 */
private int code;
/** 结果说明。如果接口调用出错,那么返回错误描述。成功则返回 ok */
private String msg;
/** 接口是否调用成功 */
private boolean success;
/** 错误信息或提示信息 */
private String message;
/** 接口返回的数据 */
private Integer data;
}

View File

@ -0,0 +1,11 @@
package net.lab1024.sa.admin.extend.app.Video;
import net.lab1024.sa.admin.extend.app.Base;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class Video extends Base {
}

View File

@ -0,0 +1,55 @@
package net.lab1024.sa.admin.extend.app.label;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class GetLabelsResponse
{
/** 接口调用状态。200正常其它值调用出错 */
private int code;
/** 结果说明。如果接口调用出错,那么返回错误描述。成功则返回 ok */
private String msg;
/** 接口返回的用户信息数据 */
private List<GetLabelsData> data;
/** 接口是否调用成功 */
private boolean success;
/** 错误信息或提示信息 */
private String message;
/**
* 根据统一标签列表 - 详细数据
*/
@Data
public static class GetLabelsData {
/**
* 标签名称
*/
private String name;
/**
* id
*/
private String id;
/**
* 子标签数量
*/
@JsonProperty("children_size")
private Integer childrenSize;
/**
* p_id
*/
@JsonProperty("p_id")
private String pId;
}
}

View File

@ -0,0 +1,68 @@
package net.lab1024.sa.admin.extend.app.label;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import net.lab1024.sa.admin.config.AppConfig;
import net.lab1024.sa.base.common.exception.BusinessException;
import net.lab1024.sa.admin.extend.app.Base;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class Label extends Base {
@Resource
private AppConfig appConfig;
// 根据医院唯一标识获取医院数据
public GetLabelsResponse getLabels(String p_id) throws BusinessException {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 处理参数
Map<String, Object> requestData = new HashMap<>();
requestData.put("p_id", p_id);
requestData.put("platform", appConfig.getPlatform());
requestData.put("timestamp", timestamp);
// 生成签名
String sign = genSignature(requestData,appConfig.getSecretKey());
String url = appConfig.getApiUrl() + "/expert-api/getLabels";
String jsonBody = JSONUtil.toJsonStr(requestData);
log.info("获取app数据参数:{}",jsonBody);
try(HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.header("sign", sign)
.body(jsonBody)
.execute()){
if (response.getStatus() != 200) {
throw new BusinessException("失败");
}
// 反序列化 JSON
GetLabelsResponse result = JSONUtil.toBean(response.body(), GetLabelsResponse.class);
log.info("获取app数据返回:{}",result);
if (result.getCode() != 200){
if (!Objects.equals(result.getMsg(), "")){
throw new BusinessException(result.getMsg());
}else{
throw new BusinessException("失败");
}
}
if (result.getData() == null){
throw new BusinessException("失败");
}
return result;
}
}
}

View File

@ -0,0 +1 @@
// 此文件已移动到 config 目录请使用 net.lab1024.sa.admin.config.EnvConfig

View File

@ -0,0 +1 @@
// 此文件已移动到 config 目录请使用 net.lab1024.sa.admin.config.WxMaConfig

View File

@ -0,0 +1,64 @@
package net.lab1024.sa.admin.extend.weChat;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
@Slf4j
public class WxMaRedisConfig extends WxMaDefaultConfigImpl {
private final RedisTemplate<String, Object> redisTemplate;
private final RedisTemplate<String, Object> prodRedisTemplate;
public WxMaRedisConfig(RedisTemplate<String, Object> redisTemplate, RedisTemplate<String, Object> prodRedisTemplate) {
this.redisTemplate = redisTemplate;
this.prodRedisTemplate = prodRedisTemplate;
}
@Override
public String getAccessToken() {
String redisKey = "wx:ma:access_token:" + this.getAppid();
String token = (String) redisTemplate.opsForValue().get(redisKey);
if (token != null) {
return token;
}
// 2. 再尝试从 prodRedis 获取
token = (String) prodRedisTemplate.opsForValue().get(redisKey);
if (token != null) {
return token;
}
synchronized (this) {
token = (String) redisTemplate.opsForValue().get(redisKey);
if (token != null) return token;
token = (String) prodRedisTemplate.opsForValue().get(redisKey);
if (token != null) return token;
try {
String newToken = super.getAccessToken(); // 强制刷新
long expireSeconds = this.getExpiresTime() - 200L;
redisTemplate.opsForValue().set(redisKey, newToken, expireSeconds, TimeUnit.SECONDS);
prodRedisTemplate.opsForValue().set(redisKey, newToken, expireSeconds, TimeUnit.SECONDS);
return newToken;
} catch (Exception e) {
log.error("获取access_token失败", e);
throw new RuntimeException("获取access_token失败", e);
}
}
}
@Override
public void updateAccessToken(String accessToken, int expiresInSeconds) {
String redisKey = "wx:ma:access_token:" + this.getAppid();
long expireSeconds = Math.max(expiresInSeconds - 200L, 1L);
redisTemplate.opsForValue().set(redisKey, accessToken, expireSeconds, TimeUnit.SECONDS);
prodRedisTemplate.opsForValue().set(redisKey, accessToken, expireSeconds, TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1,43 @@
package net.lab1024.sa.admin.extend.weChat;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import net.lab1024.sa.admin.config.WxMaConfig;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
public class WxMaServiceConfig {
private final RedisTemplate<String, Object> devRedis;
private final RedisTemplate<String, Object> prodRedis;
@Resource
private WxMaConfig wxMaConfig;
public WxMaServiceConfig(
@Qualifier("redisTemplate") RedisTemplate<String, Object> devRedis,
@Qualifier("prodRedisTemplate") RedisTemplate<String, Object> prodRedis
) {
this.devRedis = devRedis;
this.prodRedis = prodRedis;
}
@Bean
public WxMaService wxMaService() {
WxMaRedisConfig redisConfig = new WxMaRedisConfig(devRedis, prodRedis);
redisConfig.setAppid(wxMaConfig.getAppid());
redisConfig.setSecret(wxMaConfig.getSecret());
WxMaServiceImpl service = new WxMaServiceImpl();
service.setWxMaConfig(redisConfig);
return service;
}
}

View File

@ -0,0 +1,88 @@
package net.lab1024.sa.admin.extend.weChat;
import cn.binarywang.wx.miniapp.api.WxMaQrcodeService;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaCodeLineColor;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import net.lab1024.sa.admin.config.EnvConfig;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Slf4j
@Component
@RequiredArgsConstructor
public class WxMaServiceUtils {
private final WxMaService wxMaService;
@Resource
private EnvConfig envConfig;
/**
* 通过 code 换取 session 信息openid/unionid
*/
public WxMaJscode2SessionResult getSessionInfo(String code) {
try {
return wxMaService.getUserService().getSessionInfo(code);
} catch (WxErrorException e) {
log.error("获取 session 失败", e);
throw new RuntimeException("微信 session 获取失败", e);
}
}
/**
* 解密手机号信息
*/
public WxMaPhoneNumberInfo getPhoneNumber(String code) {
try {
return wxMaService.getUserService().getPhoneNumber(code); // 注意此方法为新版本推荐方法
} catch (WxErrorException e) {
log.error("获取手机号失败", e);
throw new RuntimeException("微信手机号获取失败", e);
}
}
/**
* 获取当前 access_token仅调试或日志用
*/
public String getAccessToken() {
try {
return wxMaService.getAccessToken();
} catch (WxErrorException e) {
log.error("获取 access_token 失败", e);
throw new RuntimeException("获取 access_token 失败", e);
}
}
/**
* 获取永久小程序码base64 或保存成文件都可以
* @param scene 场景值最长32个可见字符只能是数字英文下划线减号
* @param page 跳转页面路径 pages/index/index
* @return 二进制图片数据image/jpeg
*/
public byte[] getUnlimitedQrcode(String scene, String page) {
boolean checkPath = false; // 不校验 page 是否存在
String envVersion = "release"; // 可选trialdeveloprelease
if (Objects.equals(envConfig.getActive(), "dev")){
envVersion = "trial";
}
int width = 430;
boolean autoColor = true;
WxMaCodeLineColor lineColor = new WxMaCodeLineColor();
boolean isHyaline = true;
try {
WxMaQrcodeService qrcodeService = wxMaService.getQrcodeService();
return qrcodeService.createWxaCodeUnlimitBytes(scene,page,checkPath,envVersion,width,autoColor,lineColor,isHyaline);
} catch (Exception e) {
throw new RuntimeException("获取小程序码失败", e);
}
}
}

View File

@ -0,0 +1,143 @@
package net.lab1024.sa.admin.interceptor;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.admin.module.system.login.domain.RequestEmployee;
import net.lab1024.sa.admin.module.system.login.service.LoginService;
import net.lab1024.sa.base.common.annoation.NoNeedLogin;
import net.lab1024.sa.base.common.code.SystemErrorCode;
import net.lab1024.sa.base.common.code.UserErrorCode;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.util.SmartRequestUtil;
import net.lab1024.sa.base.common.util.SmartResponseUtil;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.lang.reflect.Method;
/**
* admin 拦截器
*
* @Author 1024创新实验室-主任:卓大
* @Date 2023/7/26 20:20:33
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>Since 2012
*/
@Component
@Slf4j
public class AdminInterceptor implements HandlerInterceptor {
@Resource
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// OPTIONS请求直接return
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return false;
}
boolean isHandler = handler instanceof HandlerMethod;
if (!isHandler) {
return true;
}
try {
// --------------- 第一步 根据token 获取用户 ---------------
String tokenValue = StpUtil.getTokenValue();
String loginId = (String) StpUtil.getLoginIdByToken(tokenValue);
RequestEmployee requestEmployee = loginService.getLoginEmployee(loginId, request);
// --------------- 第二步 校验 登录 ---------------
Method method = ((HandlerMethod) handler).getMethod();
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
if (noNeedLogin != null) {
checkActiveTimeout(requestEmployee);
return true;
}
if (requestEmployee == null) {
SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID));
return false;
}
// 检测token 活跃频率
checkActiveTimeout(requestEmployee);
// --------------- 第三步 校验 权限 ---------------
SmartRequestUtil.setRequestUser(requestEmployee);
if (SaAnnotationStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {
return true;
}
// 如果是超级管理员的话不需要校验权限
if (requestEmployee.getAdministratorFlag()) {
return true;
}
SaAnnotationStrategy.instance.checkMethodAnnotation.accept(method);
} catch (SaTokenException e) {
/*
* sa-token 异常状态码
* 具体请看 https://sa-token.cc/doc.html#/fun/exception-code
*/
int code = e.getCode();
if (code == 11041 || code == 11051) {
SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.NO_PERMISSION));
} else if (code == 11016) {
SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_ACTIVE_TIMEOUT));
} else if (code >= 11011 && code <= 11015) {
SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID));
} else {
SmartResponseUtil.write(response, ResponseDTO.error(UserErrorCode.PARAM_ERROR));
}
return false;
} catch (Throwable e) {
SmartResponseUtil.write(response, ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR));
log.error(e.getMessage(), e);
return false;
}
// 通过验证
return true;
}
/**
* 检测token 最低活跃频率单位如果 token 超过此时间没有访问系统就会被冻结
*/
private void checkActiveTimeout(RequestEmployee requestEmployee) {
// 用户不在线也不用检测
if (requestEmployee == null) {
return;
}
StpUtil.checkActiveTimeout();
StpUtil.updateLastActiveToNow();
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清除上下文
SmartRequestUtil.remove();
}
}

View File

@ -0,0 +1,53 @@
package net.lab1024.sa.admin.module.business.area.controller;
import net.lab1024.sa.admin.module.business.area.domain.form.AreaQueryForm;
import net.lab1024.sa.admin.module.business.area.domain.vo.AreaVO;
import net.lab1024.sa.admin.module.business.area.service.AreaService;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.List;
/**
* 省市区 Controller
*
* @Author xing
* @Date 2025-08-04 15:00:58
* @Copyright gdxz
*/
@RestController
@Tag(name = "省市区")
public class AreaController {
@Resource
private AreaService areaService;
@Operation(summary = "分页查询 @author xing")
@PostMapping("/area/queryPage")
@SaCheckPermission("area:query")
public ResponseDTO<PageResult<AreaVO>> queryPage(@RequestBody @Valid AreaQueryForm queryForm) {
return ResponseDTO.ok(areaService.queryPage(queryForm));
}
@Operation(summary = "获取省份 @author xing")
@GetMapping("/area/provList")
@SaCheckPermission("area:query")
public ResponseDTO<List<AreaVO>> provList() {
return ResponseDTO.ok(areaService.provList());
}
@Operation(summary = "获取市区 @author xing")
@GetMapping("/area/cityList/{parent}")
@SaCheckPermission("area:query")
public ResponseDTO<List<AreaVO>> cityList(@PathVariable Long parent) {
return ResponseDTO.ok(areaService.cityList(parent));
}
}

View File

@ -0,0 +1,47 @@
package net.lab1024.sa.admin.module.business.area.dao;
import java.util.List;
import net.lab1024.sa.admin.module.business.area.domain.entity.AreaEntity;
import net.lab1024.sa.admin.module.business.area.domain.form.AreaQueryForm;
import net.lab1024.sa.admin.module.business.area.domain.vo.AreaVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;
/**
* 省市区 Dao
*
* @Author xing
* @Date 2025-08-04 15:00:58
* @Copyright gdxz
*/
@Mapper
public interface AreaDao extends BaseMapper<AreaEntity> {
/**
* 分页 查询
*
* @param page
* @param queryForm
* @return
*/
List<AreaVO> queryPage(Page page, @Param("queryForm") AreaQueryForm queryForm);
/**
* 获取省份列表
* @return
*/
@Select("select * from t_area where parent is null")
List<AreaVO> provList();
/**
* 获取城市列表
* @return
*/
@Select("select * from t_area where parent = #{parent}")
List<AreaVO> cityList(long parent);
}

View File

@ -0,0 +1,62 @@
package net.lab1024.sa.admin.module.business.area.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 省市区 实体类
*
* @Author xing
* @Date 2025-08-04 15:00:58
* @Copyright gdxz
*/
@Data
@TableName("area")
public class AreaEntity {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 创建时间
*/
private LocalDateTime createDate;
/**
* 更新时间
*/
private LocalDateTime modifyDate;
/**
* 排序
*/
private Integer orders;
/**
* 全名
*/
private String fullName;
/**
* 名称
*/
private String name;
/**
* 树路径
*/
private String treePath;
/**
* 父节点
*/
private Long parent;
}

View File

@ -0,0 +1,26 @@
package net.lab1024.sa.admin.module.business.area.domain.form;
import net.lab1024.sa.base.common.domain.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 省市区 分页查询表单
*
* @Author xing
* @Date 2025-08-04 15:00:58
* @Copyright gdxz
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class AreaQueryForm extends PageParam {
@Schema(description = "名称")
private String keywords;
@Schema(description = "上级城市")
private Long parent;
}

View File

@ -0,0 +1,43 @@
package net.lab1024.sa.admin.module.business.area.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 省市区 列表VO
*
* @Author xing
* @Date 2025-08-04 15:00:58
* @Copyright gdxz
*/
@Data
public class AreaVO {
@Schema(description = "主键")
private Long id;
@Schema(description = "创建时间")
private LocalDateTime createDate;
@Schema(description = "更新时间")
private LocalDateTime modifyDate;
@Schema(description = "排序")
private Integer orders;
@Schema(description = "全名")
private String fullName;
@Schema(description = "名称")
private String name;
@Schema(description = "树路径")
private String treePath;
@Schema(description = "父节点")
private Long parent;
}

View File

@ -0,0 +1,20 @@
package net.lab1024.sa.admin.module.business.area.manager;
import net.lab1024.sa.admin.module.business.area.domain.entity.AreaEntity;
import net.lab1024.sa.admin.module.business.area.dao.AreaDao;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* 省市区 Manager
*
* @Author xing
* @Date 2025-08-04 15:00:58
* @Copyright gdxz
*/
@Service
public class AreaManager extends ServiceImpl<AreaDao, AreaEntity> {
}

View File

@ -0,0 +1,49 @@
package net.lab1024.sa.admin.module.business.area.service;
import java.util.List;
import net.lab1024.sa.admin.module.business.area.dao.AreaDao;
import net.lab1024.sa.admin.module.business.area.domain.entity.AreaEntity;
import net.lab1024.sa.admin.module.business.area.domain.form.AreaQueryForm;
import net.lab1024.sa.admin.module.business.area.domain.vo.AreaVO;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
/**
* 省市区 Service
*
* @Author xing
* @Date 2025-08-04 15:00:58
* @Copyright gdxz
*/
@Service
public class AreaService {
@Resource
private AreaDao areaDao;
/**
* 分页查询
*/
public PageResult<AreaVO> queryPage(AreaQueryForm queryForm) {
Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
List<AreaVO> list = areaDao.queryPage(page, queryForm);
return SmartPageUtil.convert2PageResult(page, list);
}
public List<AreaVO> provList() {
return areaDao.provList();
}
public List<AreaVO> cityList(Long parent) {
return areaDao.cityList(parent);
}
}

View File

@ -0,0 +1,48 @@
package net.lab1024.sa.admin.module.business.basicHospital.controller;
import net.lab1024.sa.admin.module.business.basicHospital.domain.form.BasicHospitalQueryForm;
import net.lab1024.sa.admin.module.business.basicHospital.domain.form.BasicHospitalQueryListForm;
import net.lab1024.sa.admin.module.business.basicHospital.domain.vo.BasicHospitalVO;
import net.lab1024.sa.admin.module.business.basicHospital.service.BasicHospitalService;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.List;
/**
* 基础数据-医院 Controller
*
* @Author xing
* @Date 2025-08-04 14:06:17
* @Copyright gdxz
*/
@RestController
@Tag(name = "基础数据-医院")
public class BasicHospitalController {
@Resource
private BasicHospitalService basicHospitalService;
@Operation(summary = "分页查询 @author xing")
@PostMapping("/basicHospital/queryPage")
@SaCheckPermission("basicHospital:query")
public ResponseDTO<PageResult<BasicHospitalVO>> queryPage(@RequestBody @Valid BasicHospitalQueryForm queryForm) {
return ResponseDTO.ok(basicHospitalService.queryPage(queryForm));
}
@Operation(summary = "列表查询 @author xing")
@PostMapping("/basicHospital/queryList")
@SaCheckPermission("basicHospital:list")
public ResponseDTO<List<BasicHospitalVO>> queryList(@RequestBody @Valid BasicHospitalQueryListForm queryForm) {
return ResponseDTO.ok(basicHospitalService.queryList(queryForm));
}
}

View File

@ -0,0 +1,41 @@
package net.lab1024.sa.admin.module.business.basicHospital.dao;
import java.util.List;
import net.lab1024.sa.admin.module.business.basicHospital.domain.entity.BasicHospitalEntity;
import net.lab1024.sa.admin.module.business.basicHospital.domain.form.BasicHospitalQueryForm;
import net.lab1024.sa.admin.module.business.basicHospital.domain.form.BasicHospitalQueryListForm;
import net.lab1024.sa.admin.module.business.basicHospital.domain.vo.BasicHospitalVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
/**
* 基础数据-医院 Dao
*
* @Author xing
* @Date 2025-08-04 14:06:17
* @Copyright gdxz
*/
@Mapper
public interface BasicHospitalDao extends BaseMapper<BasicHospitalEntity> {
/**
* 分页 查询
*
* @param page
* @param queryForm
* @return
*/
List<BasicHospitalVO> queryPage(Page page, @Param("queryForm") BasicHospitalQueryForm queryForm);
/**
* 列表 查询
*
* @param queryForm
* @return
*/
List<BasicHospitalVO> queryList(@Param("queryForm") BasicHospitalQueryListForm queryForm);
}

View File

@ -0,0 +1,81 @@
package net.lab1024.sa.admin.module.business.basicHospital.domain.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 基础数据-医院 实体类
*
* @Author xing
* @Date 2025-08-04 14:06:17
* @Copyright gdxz
*/
@Data
@TableName("basic_hospital")
public class BasicHospitalEntity {
/**
* 主键id
*/
@TableId
private Long hospitalId;
/**
* app唯一标识
*/
private String hospitalIden;
/**
* 医院名称
*/
private String hospitalName;
/**
* 来源2:肝胆相照 3:佳动例
*/
private Integer source;
/**
* 医院等级
*/
private String hospitalLevel;
/**
* 医生数量
*/
private Integer doctorNumber;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 区县
*/
private String county;
/**
* 地址
*/
private String address;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,28 @@
package net.lab1024.sa.admin.module.business.basicHospital.domain.form;
import net.lab1024.sa.base.common.domain.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import net.lab1024.sa.base.common.swagger.SchemaEnum;
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
/**
* 基础数据-医院 分页查询表单
*
* @Author xing
* @Date 2025-08-04 14:06:17
* @Copyright gdxz
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class BasicHospitalQueryForm extends PageParam {
@Schema(description = "医院名称")
private String keywords;
@Schema(description = "医院等级")
private String hospitalLevel;
}

View File

@ -0,0 +1,29 @@
package net.lab1024.sa.admin.module.business.basicHospital.domain.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import net.lab1024.sa.base.common.domain.PageParam;
/**
* 基础数据-医院 列表查询表单
*
* @Author xing
* @Date 2025-08-04 14:06:17
* @Copyright gdxz
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class BasicHospitalQueryListForm {
@Schema(description = "医院名称")
private String keywords;
@Schema(description = "医院等级")
private String hospitalLevel;
@Schema(description = "数量")
private Integer limit = 10;
}

View File

@ -0,0 +1,55 @@
package net.lab1024.sa.admin.module.business.basicHospital.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 基础数据-医院 列表VO
*
* @Author xing
* @Date 2025-08-04 14:06:17
* @Copyright gdxz
*/
@Data
public class BasicHospitalVO {
@Schema(description = "主键id")
private Long hospitalId;
@Schema(description = "app唯一标识")
private String hospitalIden;
@Schema(description = "医院名称")
private String hospitalName;
@Schema(description = "来源2:肝胆相照 3:佳动例)")
private Integer source;
@Schema(description = "医院等级")
private String hospitalLevel;
@Schema(description = "医生数量")
private Integer doctorNumber;
@Schema(description = "省份")
private String province;
@Schema(description = "城市")
private String city;
@Schema(description = "区县")
private String county;
@Schema(description = "地址")
private String address;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "修改时间")
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,20 @@
package net.lab1024.sa.admin.module.business.basicHospital.manager;
import net.lab1024.sa.admin.module.business.basicHospital.domain.entity.BasicHospitalEntity;
import net.lab1024.sa.admin.module.business.basicHospital.dao.BasicHospitalDao;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* 基础数据-医院 Manager
*
* @Author xing
* @Date 2025-08-04 14:06:17
* @Copyright gdxz
*/
@Service
public class BasicHospitalManager extends ServiceImpl<BasicHospitalDao, BasicHospitalEntity> {
}

View File

@ -0,0 +1,48 @@
package net.lab1024.sa.admin.module.business.basicHospital.service;
import java.util.List;
import net.lab1024.sa.admin.module.business.basicHospital.dao.BasicHospitalDao;
import net.lab1024.sa.admin.module.business.basicHospital.domain.entity.BasicHospitalEntity;
import net.lab1024.sa.admin.module.business.basicHospital.domain.form.BasicHospitalQueryForm;
import net.lab1024.sa.admin.module.business.basicHospital.domain.form.BasicHospitalQueryListForm;
import net.lab1024.sa.admin.module.business.basicHospital.domain.vo.BasicHospitalVO;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
/**
* 基础数据-医院 Service
*
* @Author xing
* @Date 2025-08-04 14:06:17
* @Copyright gdxz
*/
@Service
public class BasicHospitalService {
@Resource
private BasicHospitalDao basicHospitalDao;
/**
* 分页查询
*/
public PageResult<BasicHospitalVO> queryPage(BasicHospitalQueryForm queryForm) {
Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
List<BasicHospitalVO> list = basicHospitalDao.queryPage(page, queryForm);
return SmartPageUtil.convert2PageResult(page, list);
}
/**
* 列表查询
*/
public List<BasicHospitalVO> queryList(BasicHospitalQueryListForm queryForm) {
return basicHospitalDao.queryList(queryForm);
}
}

View File

@ -0,0 +1,40 @@
package net.lab1024.sa.admin.module.business.basicSensitiveWord.controller;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.form.BasicSensitiveWordQueryForm;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.vo.BasicSensitiveWordVO;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.service.BasicSensitiveWordService;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
/**
* 基础数据-敏感词 Controller
*
* @Author xing
* @Date 2025-08-11 00:32:07
* @Copyright gdxz
*/
@RestController
@Tag(name = "基础数据-敏感词")
public class BasicSensitiveWordController {
@Resource
private BasicSensitiveWordService basicSensitiveWordService;
@Operation(summary = "分页查询 @author xing")
@PostMapping("/basicSensitiveWord/queryPage")
@SaCheckPermission("basicSensitiveWord:query")
public ResponseDTO<PageResult<BasicSensitiveWordVO>> queryPage(@RequestBody @Valid BasicSensitiveWordQueryForm queryForm) {
return ResponseDTO.ok(basicSensitiveWordService.queryPage(queryForm));
}
}

View File

@ -0,0 +1,33 @@
package net.lab1024.sa.admin.module.business.basicSensitiveWord.dao;
import java.util.List;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.entity.BasicSensitiveWordEntity;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.form.BasicSensitiveWordQueryForm;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.vo.BasicSensitiveWordVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
/**
* 基础数据-敏感词 Dao
*
* @Author xing
* @Date 2025-08-11 00:32:07
* @Copyright gdxz
*/
@Mapper
public interface BasicSensitiveWordDao extends BaseMapper<BasicSensitiveWordEntity> {
/**
* 分页 查询
*
* @param page
* @param queryForm
* @return
*/
List<BasicSensitiveWordVO> queryPage(Page page, @Param("queryForm") BasicSensitiveWordQueryForm queryForm);
}

View File

@ -0,0 +1,41 @@
package net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 基础数据-敏感词 实体类
*
* @Author xing
* @Date 2025-08-11 00:32:07
* @Copyright gdxz
*/
@Data
@TableName("basic_sensitive_word")
public class BasicSensitiveWordEntity {
/**
* 主键id
*/
@TableId
private Long id;
/**
* 敏感词
*/
private String word;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,19 @@
package net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.form;
import net.lab1024.sa.base.common.domain.PageParam;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 基础数据-敏感词 分页查询表单
*
* @Author xing
* @Date 2025-08-11 00:32:07
* @Copyright gdxz
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class BasicSensitiveWordQueryForm extends PageParam {
}

View File

@ -0,0 +1,31 @@
package net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 基础数据-敏感词 列表VO
*
* @Author xing
* @Date 2025-08-11 00:32:07
* @Copyright gdxz
*/
@Data
public class BasicSensitiveWordVO {
@Schema(description = "主键id")
private Long id;
@Schema(description = "敏感词")
private String word;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "修改时间")
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,20 @@
package net.lab1024.sa.admin.module.business.basicSensitiveWord.manager;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.entity.BasicSensitiveWordEntity;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.dao.BasicSensitiveWordDao;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* 基础数据-敏感词 Manager
*
* @Author xing
* @Date 2025-08-11 00:32:07
* @Copyright gdxz
*/
@Service
public class BasicSensitiveWordManager extends ServiceImpl<BasicSensitiveWordDao, BasicSensitiveWordEntity> {
}

View File

@ -0,0 +1,159 @@
package net.lab1024.sa.admin.module.business.basicSensitiveWord.service;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.dao.BasicSensitiveWordDao;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.entity.BasicSensitiveWordEntity;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.form.BasicSensitiveWordQueryForm;
import net.lab1024.sa.admin.module.business.basicSensitiveWord.domain.vo.BasicSensitiveWordVO;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
/**
* 基础数据-敏感词 Service
*
* @Author xing
* @Date 2025-08-11 00:32:07
* @Copyright gdxz
*/
@Service
public class BasicSensitiveWordService {
@Resource
private BasicSensitiveWordDao basicSensitiveWordDao;
@Resource
private RedisTemplate<String, String> redisTemplate;
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 分页查询
*/
public PageResult<BasicSensitiveWordVO> queryPage(BasicSensitiveWordQueryForm queryForm) {
Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
List<BasicSensitiveWordVO> list = basicSensitiveWordDao.queryPage(page, queryForm);
return SmartPageUtil.convert2PageResult(page, list);
}
private static final String REDIS_SENSITIVE_WORD_KEY = "sensitive:word:list";
private static final long REDIS_EXPIRE_MINUTES = 30;
// 主过滤方法
public FilterResult filter(String comment) {
List<String> wordList = loadSensitiveWords();
if (wordList == null || wordList.isEmpty()) {
return new FilterResult(comment, 0);
}
TrieNode root = buildTrie(wordList);
return doFilter(comment, root);
}
// 加载敏感词可替换为数据库查询
private List<String> loadSensitiveWords() {
List<String> wordList = new ArrayList<>();
// 尝试从 Redis 获取
String wordJson = redisTemplate.opsForValue().get(REDIS_SENSITIVE_WORD_KEY);
if (wordJson != null && !wordJson.isEmpty()) {
try {
// 反序列化 JSON 字符串为 List<String>
wordList = objectMapper.readValue(wordJson, new TypeReference<List<String>>() {});
} catch (Exception e) {
return wordList;
}
}else{
// Redis 无数据则查询 DB
LambdaQueryWrapper<BasicSensitiveWordEntity> queryWrapper = new LambdaQueryWrapper<>();
List<BasicSensitiveWordEntity> basicSensitiveWords = basicSensitiveWordDao.selectList(queryWrapper); // 你自己的 DAO 方法
if (basicSensitiveWords != null && !basicSensitiveWords.isEmpty()) {
wordList = basicSensitiveWords.stream()
.map(BasicSensitiveWordEntity::getWord)
.filter(Objects::nonNull)
.collect(Collectors.toList());
try {
String wordListJson = objectMapper.writeValueAsString(wordList);
redisTemplate.opsForValue().set(REDIS_SENSITIVE_WORD_KEY, wordListJson, Duration.ofMinutes(REDIS_EXPIRE_MINUTES));
} catch (Exception e) {
return wordList;
}
}
}
return wordList;
}
// 构建字典树
private TrieNode buildTrie(List<String> words) {
TrieNode root = new TrieNode();
for (String word : words) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
node.children.putIfAbsent(ch, new TrieNode());
node = node.children.get(ch);
}
node.isEnd = true;
}
return root;
}
// 实际过滤逻辑
private FilterResult doFilter(String comment, TrieNode root) {
char[] chars = comment.toCharArray();
int isSensitive = 0;
for (int i = 0; i < chars.length; i++) {
TrieNode node = root;
int j = i;
while (j < chars.length && node.children.containsKey(chars[j])) {
node = node.children.get(chars[j]);
if (node.isEnd) {
for (int k = i; k <= j; k++) {
chars[k] = '*';
}
isSensitive = 1;
break;
}
j++;
}
}
return new FilterResult(new String(chars), isSensitive);
}
// Trie 节点内部类
private static class TrieNode {
boolean isEnd = false;
Map<Character, TrieNode> children = new HashMap<>();
}
// 返回结果封装
public static class FilterResult {
public final String comment;
public final int hasSensitive;
public FilterResult(String comment, int hasSensitive) {
this.comment = comment;
this.hasSensitive = hasSensitive;
}
}
}

View File

@ -0,0 +1,245 @@
package net.lab1024.sa.admin.module.business.caseClinical.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import net.lab1024.sa.admin.module.business.caseClinicalVideo.dao.CaseClinicalVideoDao;
import net.lab1024.sa.admin.module.business.statsCaseClinical.dao.StatsCaseClinicalDao;
import jakarta.annotation.Resource;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.dao.CaseClinicalArticleDao;
import net.lab1024.sa.admin.module.business.statsCaseClinical.dao.StatsCaseClinicalDoctorDao;
import net.lab1024.sa.admin.module.business.statsCaseClinical.dao.StatsCaseClinicalHospitalDao;
import net.lab1024.sa.admin.module.business.statsCaseClinical.dao.StatsCaseClinicalLabelDao;
import net.lab1024.sa.admin.module.business.statsCaseClinical.domain.entity.StatsCaseClinicalDoctorEntity;
import net.lab1024.sa.admin.module.business.statsCaseClinical.domain.entity.StatsCaseClinicalHospitalEntity;
import net.lab1024.sa.admin.module.business.statsCaseClinical.domain.entity.StatsCaseClinicalLabelEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
public class CaseClinicalService {
@Resource
private CaseClinicalArticleDao caseClinicalArticleDao;
@Resource
private CaseClinicalVideoDao caseClinicalVideoDao;
@Resource
private StatsCaseClinicalDoctorDao statsCaseClinicalDoctorDao;
@Resource
private StatsCaseClinicalHospitalDao statsCaseClinicalHospitalDao;
@Resource
private StatsCaseClinicalLabelDao statsCaseClinicalLabelDao;
/**
* 新增标签统计
* @param labelIden
* @param labelName
* @param type 类型 1:文章 2:视频
* @param lastPushDate
* @return
*/
@Transactional
public StatsCaseClinicalLabelEntity IncStatsCaseClinicalLabel(String labelIden, String labelName, Integer type, LocalDateTime lastPushDate){
LambdaQueryWrapper<StatsCaseClinicalLabelEntity> mapQueryWrapper = new LambdaQueryWrapper<>();
mapQueryWrapper.eq(StatsCaseClinicalLabelEntity::getLabelIden, labelIden);
StatsCaseClinicalLabelEntity statsCaseClinicalLabel = statsCaseClinicalLabelDao.selectOne(mapQueryWrapper);
if (statsCaseClinicalLabel == null) {
statsCaseClinicalLabel = new StatsCaseClinicalLabelEntity();
statsCaseClinicalLabel.setLabelIden(labelIden);
statsCaseClinicalLabel.setLabelName(labelName);
if (type == 1){
statsCaseClinicalLabel.setArticleNum(1);
}else if(type == 2){
statsCaseClinicalLabel.setVideoNum(1);
}
if (lastPushDate!=null){
statsCaseClinicalLabel.setLastPushDate(lastPushDate);
}
statsCaseClinicalLabelDao.insert(statsCaseClinicalLabel);
}else{
if (type == 1){
statsCaseClinicalLabelDao.inc(statsCaseClinicalLabel.getLabelIden(),"article_num",1);
}else{
statsCaseClinicalLabelDao.inc(statsCaseClinicalLabel.getLabelIden(),"video_num",1);
}
}
return statsCaseClinicalLabel;
}
/**
* 减少标签统计
* @param labelIden
* @param type 类型 1:文章 2:视频
* @return
*/
@Transactional
public StatsCaseClinicalLabelEntity DecStatsCaseClinicalLabel(String labelIden,Integer type){
LambdaQueryWrapper<StatsCaseClinicalLabelEntity> mapQueryWrapper = new LambdaQueryWrapper<>();
mapQueryWrapper.eq(StatsCaseClinicalLabelEntity::getLabelIden, labelIden);
StatsCaseClinicalLabelEntity statsCaseClinicalLabel = statsCaseClinicalLabelDao.selectOne(mapQueryWrapper);
if (statsCaseClinicalLabel == null) {
return null;
}else{
if (type == 1){
// 最后一篇文章发表时间
LocalDateTime lastPushDate = caseClinicalArticleDao.selectLastArticlePushDateByLabelId(labelIden);
if (lastPushDate != null){
statsCaseClinicalLabel.setLastPushDate(lastPushDate);
statsCaseClinicalLabelDao.updateById(statsCaseClinicalLabel);
}
statsCaseClinicalLabelDao.dec(statsCaseClinicalLabel.getLabelIden(),"article_num",1);
}else{
// 最后一篇文章发表时间
LocalDateTime lastPushDate = caseClinicalVideoDao.selectLastVideoPushDateByLabelId(labelIden);
if (lastPushDate != null){
statsCaseClinicalLabel.setLastPushDate(lastPushDate);
statsCaseClinicalLabelDao.updateById(statsCaseClinicalLabel);
}
statsCaseClinicalLabelDao.dec(statsCaseClinicalLabel.getLabelIden(),"video_num",1);
}
}
return statsCaseClinicalLabel;
}
// 新增医生统计
@Transactional
public StatsCaseClinicalDoctorEntity IncStatsCaseClinicalDoctor(String doctorId, Integer type, LocalDateTime lastPushDate){
LambdaQueryWrapper<StatsCaseClinicalDoctorEntity> mapQueryWrapper = new LambdaQueryWrapper<>();
mapQueryWrapper.eq(StatsCaseClinicalDoctorEntity::getDoctorId, doctorId);
StatsCaseClinicalDoctorEntity statsCaseClinicalDoctor = statsCaseClinicalDoctorDao.selectOne(mapQueryWrapper);
if (statsCaseClinicalDoctor == null) {
statsCaseClinicalDoctor = new StatsCaseClinicalDoctorEntity();
statsCaseClinicalDoctor.setDoctorId(Long.valueOf(doctorId));
if (type == 1){
statsCaseClinicalDoctor.setArticleNum(1);
}else if(type == 2){
statsCaseClinicalDoctor.setVideoNum(1);
}
if (lastPushDate!=null){
statsCaseClinicalDoctor.setLastPushDate(lastPushDate);
}
statsCaseClinicalDoctorDao.insert(statsCaseClinicalDoctor);
}else{
if (type == 1){
statsCaseClinicalDoctorDao.inc(statsCaseClinicalDoctor.getDoctorId(),"article_num",1);
}else{
statsCaseClinicalDoctorDao.inc(statsCaseClinicalDoctor.getDoctorId(),"video_num",1);
}
}
return statsCaseClinicalDoctor;
}
/**
* 减少医生统计
* @param doctorId
* @param type 类型 1:文章 2:视频
* @return
*/
@Transactional
public StatsCaseClinicalDoctorEntity DecStatsCaseClinicalDoctor(String doctorId,Integer type){
LambdaQueryWrapper<StatsCaseClinicalDoctorEntity> mapQueryWrapper = new LambdaQueryWrapper<>();
mapQueryWrapper.eq(StatsCaseClinicalDoctorEntity::getDoctorId, doctorId);
StatsCaseClinicalDoctorEntity statsCaseClinicalDoctor = statsCaseClinicalDoctorDao.selectOne(mapQueryWrapper);
if (statsCaseClinicalDoctor == null) {
return null;
}else{
if (type == 1){
// 最后一篇文章发表时间
LocalDateTime lastPushDate = caseClinicalArticleDao.selectLastArticlePushDateByDoctorId(Long.valueOf(doctorId));
if (lastPushDate != null){
statsCaseClinicalDoctor.setLastPushDate(lastPushDate);
statsCaseClinicalDoctorDao.updateById(statsCaseClinicalDoctor);
}
statsCaseClinicalDoctorDao.dec(statsCaseClinicalDoctor.getDoctorId(),"article_num",1);
}else{
// 最后一篇文章发表时间
LocalDateTime lastPushDate = caseClinicalVideoDao.selectLastVideoPushDateByDoctorId(Long.valueOf(doctorId));
if (lastPushDate != null){
statsCaseClinicalDoctor.setLastPushDate(lastPushDate);
statsCaseClinicalDoctorDao.updateById(statsCaseClinicalDoctor);
}
statsCaseClinicalDoctorDao.dec(statsCaseClinicalDoctor.getDoctorId(),"video_num",1);
}
}
return statsCaseClinicalDoctor;
}
// 新增医院统计
@Transactional
public StatsCaseClinicalHospitalEntity IncStatsCaseClinicalHospital(String hospitalId, Integer type, LocalDateTime lastPushDate){
LambdaQueryWrapper<StatsCaseClinicalHospitalEntity> mapQueryWrapper = new LambdaQueryWrapper<>();
mapQueryWrapper.eq(StatsCaseClinicalHospitalEntity::getHospitalId, hospitalId);
StatsCaseClinicalHospitalEntity statsCaseClinicalHospital = statsCaseClinicalHospitalDao.selectOne(mapQueryWrapper);
if (statsCaseClinicalHospital == null) {
statsCaseClinicalHospital = new StatsCaseClinicalHospitalEntity();
statsCaseClinicalHospital.setHospitalId(Long.valueOf(hospitalId));
if (type == 1){
statsCaseClinicalHospital.setArticleNum(1);
}else if(type == 2){
statsCaseClinicalHospital.setVideoNum(1);
}
if (lastPushDate!=null){
statsCaseClinicalHospital.setLastPushDate(lastPushDate);
}
statsCaseClinicalHospitalDao.insert(statsCaseClinicalHospital);
}else{
if (type == 1){
statsCaseClinicalHospitalDao.inc(statsCaseClinicalHospital.getHospitalId(),"article_num",1);
}else{
statsCaseClinicalHospitalDao.inc(statsCaseClinicalHospital.getHospitalId(),"video_num",1);
}
}
return statsCaseClinicalHospital;
}
/**
* 减少医院统计
* @param hospitalId
* @param type 类型 1:文章 2:视频
* @return
*/
@Transactional
public StatsCaseClinicalHospitalEntity DecStatsCaseClinicalHospital(String hospitalId,Integer type){
LambdaQueryWrapper<StatsCaseClinicalHospitalEntity> mapQueryWrapper = new LambdaQueryWrapper<>();
mapQueryWrapper.eq(StatsCaseClinicalHospitalEntity::getHospitalId, hospitalId);
StatsCaseClinicalHospitalEntity statsCaseClinicalHospital = statsCaseClinicalHospitalDao.selectOne(mapQueryWrapper);
if (statsCaseClinicalHospital == null) {
return null;
}else{
if (type == 1){
// 最后一篇文章发表时间
LocalDateTime lastPushDate = caseClinicalArticleDao.selectLastArticlePushDateByHospitalId(Long.valueOf(hospitalId));
if (lastPushDate != null){
statsCaseClinicalHospital.setLastPushDate(lastPushDate);
statsCaseClinicalHospitalDao.updateById(statsCaseClinicalHospital);
}
statsCaseClinicalHospitalDao.dec(statsCaseClinicalHospital.getHospitalId(),"article_num",1);
}else{
// 最后一篇文章发表时间
LocalDateTime lastPushDate = caseClinicalVideoDao.selectLastVideoPushDateByHospitalId(Long.valueOf(hospitalId));
if (lastPushDate != null){
statsCaseClinicalHospital.setLastPushDate(lastPushDate);
statsCaseClinicalHospitalDao.updateById(statsCaseClinicalHospital);
}
statsCaseClinicalHospitalDao.dec(statsCaseClinicalHospital.getHospitalId(),"video_num",1);
}
}
return statsCaseClinicalHospital;
}
}

View File

@ -0,0 +1,113 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.controller;
import com.amazonaws.Response;
import net.lab1024.sa.admin.extend.app.label.GetLabelsResponse;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.CaseClinicalArticleAddForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.CaseClinicalArticleLabelQueryListForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.CaseClinicalArticleQueryForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.CaseClinicalArticleUpdateForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.CaseClinicalArticleStatusUpdateForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.AppCaseClinicalArticleLabelVO;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.CaseClinicalArticleVO;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.service.CaseClinicalArticleLabelService;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.service.CaseClinicalArticleService;
import net.lab1024.sa.base.common.domain.ValidateList;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestParam;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* 病例库-临床-文章 Controller
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@RestController
@Tag(name = "病例库-临床-文章")
public class CaseClinicalArticleController {
@Resource
private CaseClinicalArticleService caseClinicalArticleService;
@Resource
private CaseClinicalArticleLabelService caseClinicalArticleLabelService;
@Operation(summary = "分页查询 @author xing")
@PostMapping("/caseClinicalArticle/queryPage")
@SaCheckPermission("caseClinicalArticle:query")
public ResponseDTO<PageResult<CaseClinicalArticleVO>> queryPage(@RequestBody @Valid CaseClinicalArticleQueryForm queryForm) {
return ResponseDTO.ok(caseClinicalArticleService.queryPage(queryForm));
}
@Operation(summary = "添加 @author xing")
@PostMapping("/caseClinicalArticle/add")
@SaCheckPermission("caseClinicalArticle:add")
public ResponseDTO<String> add(@RequestBody @Valid CaseClinicalArticleAddForm addForm) {
return caseClinicalArticleService.add(addForm);
}
@Operation(summary = "更新 @author xing")
@PostMapping("/caseClinicalArticle/update")
@SaCheckPermission("caseClinicalArticle:update")
public ResponseDTO<String> update(@RequestBody @Valid CaseClinicalArticleUpdateForm updateForm) {
return caseClinicalArticleService.update(updateForm);
}
@Operation(summary = "批量删除 @author xing")
@PostMapping("/caseClinicalArticle/batchDelete")
@SaCheckPermission("caseClinicalArticle:delete")
public ResponseDTO<String> batchDelete(@RequestBody ValidateList<Long> idList) {
return caseClinicalArticleService.batchDelete(idList);
}
@Operation(summary = "单个删除 @author xing")
@GetMapping("/caseClinicalArticle/delete/{articleId}")
@SaCheckPermission("caseClinicalArticle:delete")
public ResponseDTO<String> batchDelete(@PathVariable Long articleId) {
return caseClinicalArticleService.delete(articleId);
}
@Operation(summary = "生成文章分享二维码 @author xing")
@PostMapping("/caseClinicalArticle/addUnlimitedQrcode/{articleId}")
@SaCheckPermission("caseClinicalArticle:addUnlimitedQrcode")
public ResponseDTO<String> addUnlimitedQrcode(@PathVariable Long articleId) {
caseClinicalArticleService.addUnlimitedQrcode(articleId);
return ResponseDTO.ok();
}
@Operation(summary = "获取文章详情 @author xing")
@GetMapping("/caseClinicalArticle/getDetail/{articleId}")
@SaCheckPermission("caseClinicalArticle:query")
public ResponseDTO<CaseClinicalArticleVO> getDetail(@PathVariable Long articleId) {
return caseClinicalArticleService.getDetail(articleId);
}
/**
* 获取疾病标签列表
*/
@Operation(summary = "获取疾病标签列表 @author xing")
@GetMapping("/caseClinicalLabel/queryList")
@SaCheckPermission("caseClinicalArticle:query")
public ResponseDTO<List<AppCaseClinicalArticleLabelVO>> getCaseLabel(@RequestParam(defaultValue = "0") String pId) {
return caseClinicalArticleLabelService.getCaseLabel(pId);
}
@Operation(summary = "修改状态 @author xing")
@PostMapping("/caseClinicalArticle/status/update")
@SaCheckPermission("caseClinicalArticle:update")
public ResponseDTO<String> statusUpdate(@RequestBody @Valid CaseClinicalArticleStatusUpdateForm updateForm) {
return caseClinicalArticleService.updateStatus(updateForm.getArticleId(), updateForm.getStatus());
}
}

View File

@ -0,0 +1,78 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.dao;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity.CaseClinicalArticleEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.CaseClinicalArticleQueryForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.CaseClinicalArticleVO;
import java.time.LocalDateTime;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/**
* 病例库-临床-文章 Dao
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Mapper
public interface CaseClinicalArticleDao extends BaseMapper<CaseClinicalArticleEntity> {
/**
* 分页 查询
*
* @param page
* @param queryForm
* @return
*/
List<CaseClinicalArticleVO> queryPage(Page page, @Param("queryForm") CaseClinicalArticleQueryForm queryForm);
/**
* Inc 自增
* @param articleId 文章 ID
* @param field 字段名称
* @param numeral 增加的数值
* @return 更新的行数
*/
@Update("UPDATE stats_case_clinical SET ${field} = ${field} + #{numeral} WHERE stats_id = #{statsId}")
int inc(@Param("statsId") Long articleId, @Param("field") String field, @Param("numeral") int numeral);
/**
* Dec 自减
*
* @param articleId 文章 ID
* @param field 字段名称
* @param numeral 减少的数值
* @return 更新的行数
*/
@Update("UPDATE stats_case_clinical " +
"SET ${field} = CASE WHEN ${field} >= #{numeral} THEN ${field} - #{numeral} ELSE 0 END " +
"WHERE stats_id = #{statsId}")
int dec(@Param("statsId") Long articleId, @Param("field") String field, @Param("numeral") int numeral);
/**
* 根据医院ID查询该医院最后一篇文章的发表时间
* @param hospitalId 医院ID
* @return 最新发表时间无数据时返回 null
*/
LocalDateTime selectLastArticlePushDateByHospitalId(@Param("hospitalId") Long hospitalId);
/**
* 根据医生ID查询该医生最后一篇文章的发表时间
* @param doctorId 医院ID
* @return 最新发表时间无数据时返回 null
*/
LocalDateTime selectLastArticlePushDateByDoctorId(@Param("doctorId") Long doctorId);
/**
* 根据医院ID查询该医院最后一篇文章的发表时间
* @param labelIden 标签唯一标识
* @return 最新发表时间无数据时返回 null
*/
LocalDateTime selectLastArticlePushDateByLabelId(@Param("labelIden") String labelIden);
}

View File

@ -0,0 +1,24 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.dao;
import java.util.List;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity.CaseClinicalArticleLabelEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.CaseClinicalArticleLabelVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
/**
* 病例库-临床-文章-标签 Dao
*
* @Author xing
* @Date 2025-08-06 17:41:09
* @Copyright gdxz
*/
@Mapper
public interface CaseClinicalArticleLabelDao extends BaseMapper<CaseClinicalArticleLabelEntity> {
}

View File

@ -0,0 +1,105 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.entity.CaseClinicalArticleAuthorEntity;
/**
* 病例库-临床-文章 实体类
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Data
@TableName("case_clinical_article")
public class CaseClinicalArticleEntity {
/**
* 主键id
*/
@TableId
private Long articleId;
/**
* 标题
*/
private String articleTitle;
/**
* 状态1:正常 2:禁用
*/
private Integer articleStatus;
/**
* 删除状态0: 1:
*/
private Integer deleteStatus;
/**
* 阅读量
*/
private Integer readNum;
/**
* 收藏量
*/
private Integer collectNum;
/**
* 评论数
*/
private Integer commentNum;
/**
* 证书图片
*/
private String certImage;
/**
* 发表时间
*/
private LocalDateTime pushDate;
/**
* 是否外部链接0: 1:
*/
private Integer isLink;
/**
* 外部链接地址
*/
private String isLinkUrl;
/**
* 分享二维码地址
*/
@TableField("share_qrcode")
private String shareQrcode;
/**
* 内容
*/
private String articleContent;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
// 作者
@TableField(exist = false)
private List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthor;
}

View File

@ -0,0 +1,51 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 病例库-临床-文章-标签 实体类
*
* @Author xing
* @Date 2025-08-06 17:41:09
* @Copyright gdxz
*/
@Data
@TableName("case_clinical_article_label")
public class CaseClinicalArticleLabelEntity {
/**
* 主键id
*/
@TableId
private Long articleLabelId;
/**
* 临床文章id
*/
private Long articleId;
/**
* app唯一标识
*/
private String appIden;
/**
* 标签名称
*/
private String labelName;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,59 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Data;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.vo.CaseClinicalArticleAuthorVO;
import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.CaseClinicalArticleAuthorForm;
/**
* 病例库-临床-文章 新建表单
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Data
public class CaseClinicalArticleAddForm {
@Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "标题 不能为空")
private String articleTitle;
@Schema(description = "状态1:正常 2:禁用)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "状态1:正常 2:禁用) 不能为空")
private Integer articleStatus;
@Schema(description = "发表时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "发表时间 不能为空")
private LocalDateTime pushDate;
@Schema(description = "是否外部链接0:否 1:是)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "是否外部链接0:否 1:是) 不能为空")
private Integer isLink;
@Schema(description = "外部链接地址")
private String isLinkUrl;
@Schema(description = "分享二维码地址")
@JsonDeserialize(using = FileKeyVoDeserializer.class)
private String shareQrcode;
@Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "内容 不能为空")
private String articleContent;
@Schema(description = "作者列表")
private List<CaseClinicalArticleAuthorForm> authorList;
@Schema(description = "标签列表")
private List<CaseClinicalArticleLabelForm> labelList;
}

View File

@ -0,0 +1,31 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 病例库-临床-文章-作者 表单
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@Data
public class CaseClinicalArticleAuthorForm {
@Schema(description = "医生ID")
private Long doctorId;
@Schema(description = "医生姓名")
private String doctorName;
@Schema(description = "医院名称")
private String hospitalName;
@Schema(description = "医院省份")
private String hospitalProvince;
@Schema(description = "医院城市")
private String hospitalCity;
}

View File

@ -0,0 +1,26 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 病例库-临床-文章-标签 表单
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@Data
public class CaseClinicalArticleLabelForm {
@Schema(description = "医生ID")
private Long doctorId;
@Schema(description = "唯一标识")
private String appIden; // app唯一标识
@Schema(description = "标签名称")
private String labelName; // 标签名称
}

View File

@ -0,0 +1,25 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import net.lab1024.sa.base.common.domain.PageParam;
import java.time.LocalDate;
/**
* 病例库-临床-文章 分页查询表单
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class CaseClinicalArticleLabelQueryListForm {
@Schema(description = "父级ID默认为0表示获取顶级标签")
private String pId = "0";
}

View File

@ -0,0 +1,36 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form;
import net.lab1024.sa.base.common.domain.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
/**
* 病例库-临床-文章 分页查询表单
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class CaseClinicalArticleQueryForm extends PageParam {
@Schema(description = "关键字")
private String keywords;
@Schema(description = "状态")
private Integer articleStatus;
@Schema(description = "删除状态")
private Integer deleteStatus;
@Schema(description = "发表时间")
private LocalDate pushDateBegin;
@Schema(description = "发表时间")
private LocalDate pushDateEnd;
}

View File

@ -0,0 +1,26 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
/**
* 病例库-临床-文章 状态更新表单
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Data
@Schema(description = "病例库-临床-文章 状态更新表单")
public class CaseClinicalArticleStatusUpdateForm {
@Schema(description = "文章ID")
@NotNull(message = "文章ID不能为空")
private Long articleId;
@Schema(description = "状态 1:正常 2:禁用")
@NotNull(message = "状态不能为空")
private Integer status;
}

View File

@ -0,0 +1,65 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import lombok.Data;
import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.CaseClinicalArticleAuthorForm;
import java.util.List;
/**
* 病例库-临床-文章 更新表单
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Data
public class CaseClinicalArticleUpdateForm {
@Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "主键id 不能为空")
private Long articleId;
@Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "标题 不能为空")
private String articleTitle;
@Schema(description = "状态1:正常 2:禁用)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "状态1:正常 2:禁用) 不能为空")
private Integer articleStatus;
@Schema(description = "删除状态0:否 1:是)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "删除状态0:否 1:是) 不能为空")
private Integer deleteStatus;
@Schema(description = "发表时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "发表时间 不能为空")
private LocalDateTime pushDate;
@Schema(description = "是否外部链接0:否 1:是)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "是否外部链接0:否 1:是) 不能为空")
private Integer isLink;
@Schema(description = "外部链接地址")
private String isLinkUrl;
@Schema(description = "分享二维码地址")
@JsonDeserialize(using = FileKeyVoDeserializer.class)
private String shareQrcode;
@Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "内容 不能为空")
private String articleContent;
@Schema(description = "作者列表")
public List<CaseClinicalArticleAuthorForm> authorList;
@Schema(description = "标签列表")
private List<CaseClinicalArticleLabelForm> labelList;
}

View File

@ -0,0 +1,37 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 病例库-临床-文章-标签 列表VO
*
* @Author xing
* @Date 2025-08-06 17:41:09
* @Copyright gdxz
*/
@Data
public class AppCaseClinicalArticleLabelVO {
/**
* app唯一标识
*/
@Schema(description = "唯一标识")
private String appIden;
/**
* 标签名称
*/
@Schema(description = "标签名称")
private String labelName;
/**
* 是否存在子标签0 1:
*/
@Schema(description = "是否存在子标签")
private Integer isSub = 0;
}

View File

@ -0,0 +1,37 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 病例库-临床-文章-标签 列表VO
*
* @Author xing
* @Date 2025-08-06 17:41:09
* @Copyright gdxz
*/
@Data
public class CaseClinicalArticleLabelVO {
@Schema(description = "主键id")
private Long articleLabelId;
@Schema(description = "临床文章id")
private Long articleId;
@Schema(description = "app唯一标识")
private String appIden;
@Schema(description = "标签名称")
private String labelName;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "修改时间")
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,80 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo;
import cn.hutool.core.bean.BeanUtil;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Data;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity.CaseClinicalArticleEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.vo.CaseClinicalArticleAuthorVO;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.CaseClinicalArticleLabelVO;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo.CaseClinicalDoctorVO;
import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer;
/**
* 病例库-临床-文章 列表VO
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Data
public class CaseClinicalArticleVO {
@Schema(description = "主键id")
private Long articleId;
@Schema(description = "标题")
private String articleTitle;
@Schema(description = "状态1:正常 2:禁用)")
private Integer articleStatus;
@Schema(description = "删除状态0:否 1:是)")
private Integer deleteStatus;
@Schema(description = "阅读量")
private Integer readNum;
@Schema(description = "收藏量")
private Integer collectNum;
@Schema(description = "评论数")
private Integer commentNum;
@Schema(description = "证书图片")
// @JsonSerialize(using = FileKeyVoSerializer.class)
private String certImage;
@Schema(description = "发表时间")
private LocalDateTime pushDate;
@Schema(description = "是否外部链接0:否 1:是)")
private Integer isLink;
@Schema(description = "外部链接地址")
private String isLinkUrl;
@Schema(description = "分享二维码地址")
// @JsonSerialize(using = FileKeyVoSerializer.class)
private String shareQrcode;
@Schema(description = "内容")
private String articleContent;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "修改时间")
private LocalDateTime updatedAt;
@Schema(description = "作者数据")
private List<CaseClinicalArticleAuthorVO> caseClinicalArticleAuthor;
@Schema(description = "标签数据")
private List<CaseClinicalArticleLabelVO> caseClinicalArticleLabel;
}

View File

@ -0,0 +1,20 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.manager;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity.CaseClinicalArticleLabelEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.dao.CaseClinicalArticleLabelDao;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* 病例库-临床-文章-标签 Manager
*
* @Author xing
* @Date 2025-08-06 17:41:09
* @Copyright gdxz
*/
@Service
public class CaseClinicalArticleLabelManager extends ServiceImpl<CaseClinicalArticleLabelDao, CaseClinicalArticleLabelEntity> {
}

View File

@ -0,0 +1,20 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.manager;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.dao.CaseClinicalArticleDao;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity.CaseClinicalArticleEntity;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* 病例库-临床-文章 Manager
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Service
public class CaseClinicalArticleManager extends ServiceImpl<CaseClinicalArticleDao, CaseClinicalArticleEntity> {
}

View File

@ -0,0 +1,71 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.service;
import java.util.ArrayList;
import java.util.List;
import com.amazonaws.Response;
import net.lab1024.sa.admin.extend.app.label.GetLabelsResponse;
import net.lab1024.sa.admin.extend.app.label.Label;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.dao.CaseClinicalArticleLabelDao;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity.CaseClinicalArticleLabelEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.AppCaseClinicalArticleLabelVO;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.CaseClinicalArticleLabelVO;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
/**
* 病例库-临床-文章-标签 Service
*
* @Author xing
* @Date 2025-08-06 17:41:09
* @Copyright gdxz
*/
@Slf4j
@Service
public class CaseClinicalArticleLabelService {
@Resource
private CaseClinicalArticleLabelDao caseClinicalArticleLabelDao;
@Resource
private Label label;
/**
* 获取疾病标签数据
*/
public ResponseDTO<List<AppCaseClinicalArticleLabelVO>> getCaseLabel(String pid) {
try {
GetLabelsResponse result = label.getLabels(pid);
List<GetLabelsResponse.GetLabelsData> datas = result.getData();
List<AppCaseClinicalArticleLabelVO> labelDtoList = new ArrayList<>();
for (GetLabelsResponse.GetLabelsData d : datas) {
AppCaseClinicalArticleLabelVO dto = new AppCaseClinicalArticleLabelVO();
dto.setAppIden(d.getId());
dto.setLabelName(d.getName());
if (d.getChildrenSize() > 0){
dto.setIsSub(1);
}
labelDtoList.add(dto);
}
return ResponseDTO.ok(labelDtoList);
} catch (Exception e) {
log.error("获取疾病标签数据失败: {}", e.getMessage(), e);
return ResponseDTO.userErrorParam("获取疾病标签数据失败: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,819 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticle.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import net.lab1024.sa.admin.extend.app.UserInfo.GetUserInfoResponse;
import net.lab1024.sa.admin.module.business.basicHospital.dao.BasicHospitalDao;
import net.lab1024.sa.admin.module.business.basicHospital.domain.entity.BasicHospitalEntity;
import net.lab1024.sa.admin.module.business.caseClinical.service.CaseClinicalService;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.dao.CaseClinicalArticleDao;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.dao.CaseClinicalArticleLabelDao;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity.CaseClinicalArticleEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.entity.CaseClinicalArticleLabelEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.form.*;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.CaseClinicalArticleVO;
import net.lab1024.sa.admin.module.business.caseClinicalArticle.domain.vo.CaseClinicalArticleLabelVO;
import net.lab1024.sa.admin.extend.weChat.WxMaServiceUtils;
import net.lab1024.sa.admin.extend.aliyun.Oss;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.dao.CaseClinicalDoctorCertDao;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.entity.CaseClinicalDoctorCertEntity;
import net.lab1024.sa.admin.module.business.statsCaseClinical.dao.StatsCaseClinicalDao;
import net.lab1024.sa.admin.module.business.user.service.UserService;
import net.lab1024.sa.admin.util.ImageUtil;
import net.lab1024.sa.admin.util.Replace;
import net.lab1024.sa.base.common.exception.BusinessException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Value;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.dao.CaseClinicalArticleAuthorDao;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.entity.CaseClinicalArticleAuthorEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.vo.CaseClinicalArticleAuthorVO;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.dao.CaseClinicalDoctorDao;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.entity.CaseClinicalDoctorEntity;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo.CaseClinicalDoctorVO;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import net.lab1024.sa.base.common.util.SmartResponseUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import net.lab1024.sa.admin.util.Replace;
import jakarta.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
/**
* 病例库-临床-文章 Service
*
* @Author xing
* @Date 2025-08-04 10:17:15
* @Copyright gdxz
*/
@Service
public class CaseClinicalArticleService {
@Resource
private CaseClinicalArticleDao caseClinicalArticleDao;
@Resource
private CaseClinicalService caseClinicalService;
@Resource
private StatsCaseClinicalDao statsCaseClinicalDao;
@Resource
private CaseClinicalArticleAuthorDao caseClinicalArticleAuthorDao;
@Resource
private CaseClinicalDoctorDao caseClinicalDoctorDao;
@Resource
private BasicHospitalDao basicHospitalDao;
@Resource
private WxMaServiceUtils wxMaServiceUtils;
@Resource
private CaseClinicalArticleLabelDao caseClinicalArticleLabelDao;
@Resource
private UserService userService;
@Resource
private CaseClinicalDoctorCertDao caseClinicalDoctorCertDao;
@Value("${spring.profiles.active:prod}")
private String activeProfile;
/**
* 分页查询
*/
public PageResult<CaseClinicalArticleVO> queryPage(CaseClinicalArticleQueryForm queryForm) {
Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
List<CaseClinicalArticleVO> list = caseClinicalArticleDao.queryPage(page, queryForm);
for (CaseClinicalArticleVO vo : list) {
// 获取作者数据
LambdaQueryWrapper<CaseClinicalArticleAuthorEntity> authorQueryWrapper = new LambdaQueryWrapper<>();
authorQueryWrapper.eq(CaseClinicalArticleAuthorEntity::getArticleId, vo.getArticleId());
List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthors = caseClinicalArticleAuthorDao.selectList(authorQueryWrapper);
List<CaseClinicalArticleAuthorVO> caseClinicalArticleAuthorVOs = SmartBeanUtil.copyList(caseClinicalArticleAuthors, CaseClinicalArticleAuthorVO.class);
// 为每个作者VO设置医生数据
for (CaseClinicalArticleAuthorVO authorVO : caseClinicalArticleAuthorVOs) {
// 查询医生
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectById(authorVO.getDoctorId());
if (caseClinicalDoctor != null) {
// 查询医生所属医院
BasicHospitalEntity basicHospital = basicHospitalDao.selectById(caseClinicalDoctor.getHospitalId());
if (basicHospital != null) {
caseClinicalDoctor.setBasicHospital(basicHospital);
}
// 转换为VO并设置医院信息
CaseClinicalDoctorVO caseClinicalDoctorVO = SmartBeanUtil.copy(caseClinicalDoctor, CaseClinicalDoctorVO.class);
if (basicHospital != null) {
caseClinicalDoctorVO.setHospitalName(basicHospital.getHospitalName());
caseClinicalDoctorVO.setHospitalProvince(basicHospital.getProvince());
caseClinicalDoctorVO.setHospitalCity(basicHospital.getCity());
}
authorVO.setCaseClinicalDoctor(caseClinicalDoctorVO);
}
}
vo.setCaseClinicalArticleAuthor(caseClinicalArticleAuthorVOs);
// 获取标签数据
LambdaQueryWrapper<CaseClinicalArticleLabelEntity> labelQueryWrapper = new LambdaQueryWrapper<>();
labelQueryWrapper.eq(CaseClinicalArticleLabelEntity::getArticleId, vo.getArticleId());
List<CaseClinicalArticleLabelEntity> caseClinicalArticleLabels = caseClinicalArticleLabelDao.selectList(labelQueryWrapper);
// 转换为VO
List<CaseClinicalArticleLabelVO> caseClinicalArticleLabelVOs = SmartBeanUtil.copyList(caseClinicalArticleLabels, CaseClinicalArticleLabelVO.class);
vo.setCaseClinicalArticleLabel(caseClinicalArticleLabelVOs);
}
return SmartPageUtil.convert2PageResult(page, list);
}
/**
* 添加
*/
@Transactional
public ResponseDTO<String> add(CaseClinicalArticleAddForm addForm) {
// 新增文章
CaseClinicalArticleEntity caseClinicalArticle = SmartBeanUtil.copy(addForm, CaseClinicalArticleEntity.class);
caseClinicalArticleDao.insert(caseClinicalArticle);
// 新增统计
IncClinicalArticleStats(String.valueOf(caseClinicalArticle.getArticleId()),4,1);
// 生成文章分享二维码
byte[] qrcodeBytes = addUnlimitedQrcode(caseClinicalArticle.getArticleId());
if (qrcodeBytes == null || qrcodeBytes.length == 0) {
return ResponseDTO.userErrorParam("添加失败");
}
// 处理文章作者
HandleArticleAuthor(caseClinicalArticle,addForm.getAuthorList(),qrcodeBytes);
// 处理文章标签
HandleArticleLabel(caseClinicalArticle,addForm.getLabelList());
return ResponseDTO.ok();
}
/**
* 更新
*
*/
@Transactional
public ResponseDTO<String> update(CaseClinicalArticleUpdateForm updateForm) {
// 获取文章数据
CaseClinicalArticleEntity caseClinicalArticle = caseClinicalArticleDao.selectById(updateForm.getArticleId());
if (caseClinicalArticle == null) {
throw new BusinessException("修改失败");
}
// 修改
if (!Objects.equals(caseClinicalArticle.getArticleTitle(), updateForm.getArticleTitle())) {
caseClinicalArticle.setArticleTitle(updateForm.getArticleTitle());
}
if (!Objects.equals(caseClinicalArticle.getPushDate(), updateForm.getPushDate())) {
caseClinicalArticle.setPushDate(updateForm.getPushDate());
}
if (!Objects.equals(caseClinicalArticle.getIsLink(), updateForm.getIsLink())) {
caseClinicalArticle.setIsLink(updateForm.getIsLink());
}
if (!Objects.equals(caseClinicalArticle.getIsLinkUrl(), updateForm.getIsLinkUrl())) {
caseClinicalArticle.setIsLinkUrl(updateForm.getIsLinkUrl());
}
if (!Objects.equals(caseClinicalArticle.getArticleContent(), updateForm.getArticleContent())) {
caseClinicalArticle.setArticleContent(updateForm.getArticleContent());
}
// 如果文章状态发生改变修正统计数据
if (!Objects.equals(caseClinicalArticle.getArticleStatus(), updateForm.getArticleStatus())) {
caseClinicalArticle.setArticleStatus(updateForm.getArticleStatus());
// 修改统计数量
DecClinicalArticleStats(String.valueOf(caseClinicalArticle.getArticleId()),4,1);
// 获取标签数据
LambdaQueryWrapper<CaseClinicalArticleLabelEntity> labelQueryWrapper = new LambdaQueryWrapper<>();
labelQueryWrapper.eq(CaseClinicalArticleLabelEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleLabelEntity> caseClinicalArticleLabels = caseClinicalArticleLabelDao.selectList(labelQueryWrapper);
for (CaseClinicalArticleLabelEntity label : caseClinicalArticleLabels){
// 减少标签统计
caseClinicalService.DecStatsCaseClinicalLabel(label.getAppIden(),1);
}
// 获取作者数据
LambdaQueryWrapper<CaseClinicalArticleAuthorEntity> authorQueryWrapper = new LambdaQueryWrapper<>();
authorQueryWrapper.eq(CaseClinicalArticleAuthorEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthors = caseClinicalArticleAuthorDao.selectList(authorQueryWrapper);
for (CaseClinicalArticleAuthorEntity author : caseClinicalArticleAuthors){
// 获取医生数据
LambdaQueryWrapper<CaseClinicalDoctorEntity> doctorQueryWrapper = new LambdaQueryWrapper<>();
doctorQueryWrapper.eq(CaseClinicalDoctorEntity::getDoctorId,author.getDoctorId());
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectOne(doctorQueryWrapper);
if (caseClinicalDoctor == null) {
throw new BusinessException("无法完成此操作");
}
// 减少医生统计
caseClinicalService.DecStatsCaseClinicalDoctor(String.valueOf(author.getDoctorId()),1);
// 减少医院统计
caseClinicalService.DecStatsCaseClinicalHospital(String.valueOf(caseClinicalDoctor.getHospitalId()),1);
}
}
caseClinicalArticleDao.updateById(caseClinicalArticle);
// 处理文章作者
HandleArticleAuthor(caseClinicalArticle,updateForm.getAuthorList(),null);
// 处理文章标签
HandleArticleLabel(caseClinicalArticle,updateForm.getLabelList());
return ResponseDTO.ok();
}
/**
* 批量删除
*/
@Transactional
public ResponseDTO<String> batchDelete(List<Long> idList) {
if (CollectionUtils.isEmpty(idList)){
return ResponseDTO.ok();
}
for (Long articleId : idList) {
if (null == articleId){
throw new BusinessException("删除失败");
}
CaseClinicalArticleEntity caseClinicalArticle = caseClinicalArticleDao.selectById(articleId);
if (caseClinicalArticle == null){
throw new BusinessException("删除失败");
}
// 修改为删除
caseClinicalArticle.setDeleteStatus(1);
caseClinicalArticleDao.updateById(caseClinicalArticle);
// 修改统计数量
DecClinicalArticleStats(String.valueOf(caseClinicalArticle.getArticleId()),4,1);
// 获取视频标签数据
LambdaQueryWrapper<CaseClinicalArticleLabelEntity> labelQueryWrapper = new LambdaQueryWrapper<>();
labelQueryWrapper.eq(CaseClinicalArticleLabelEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleLabelEntity> caseClinicalArticleLabels = caseClinicalArticleLabelDao.selectList(labelQueryWrapper);
for (CaseClinicalArticleLabelEntity label : caseClinicalArticleLabels){
// 减少标签统计
caseClinicalService.DecStatsCaseClinicalLabel(label.getAppIden(),1);
}
// 获取作者数据
LambdaQueryWrapper<CaseClinicalArticleAuthorEntity> authorQueryWrapper = new LambdaQueryWrapper<>();
authorQueryWrapper.eq(CaseClinicalArticleAuthorEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthors = caseClinicalArticleAuthorDao.selectList(authorQueryWrapper);
for (CaseClinicalArticleAuthorEntity author : caseClinicalArticleAuthors){
// 获取医生数据
LambdaQueryWrapper<CaseClinicalDoctorEntity> doctorQueryWrapper = new LambdaQueryWrapper<>();
doctorQueryWrapper.eq(CaseClinicalDoctorEntity::getDoctorId,author.getDoctorId());
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectOne(doctorQueryWrapper);
if (caseClinicalDoctor == null) {
throw new BusinessException("无法完成此操作");
}
// 减少医生统计
caseClinicalService.DecStatsCaseClinicalDoctor(String.valueOf(author.getDoctorId()),1);
// 减少医院统计
caseClinicalService.DecStatsCaseClinicalHospital(String.valueOf(caseClinicalDoctor.getHospitalId()),1);
}
caseClinicalArticleDao.deleteById(articleId);
}
return ResponseDTO.ok();
}
/**
* 单个删除
*/
public ResponseDTO<String> delete(Long articleId) {
if (null == articleId){
return ResponseDTO.ok();
}
CaseClinicalArticleEntity caseClinicalArticle = caseClinicalArticleDao.selectById(articleId);
if (caseClinicalArticle == null){
return ResponseDTO.userErrorParam("删除失败");
}
// 修改为删除
caseClinicalArticle.setDeleteStatus(1);
caseClinicalArticleDao.updateById(caseClinicalArticle);
// 修改统计数量
DecClinicalArticleStats(String.valueOf(caseClinicalArticle.getArticleId()),4,1);
// 获取视频标签数据
LambdaQueryWrapper<CaseClinicalArticleLabelEntity> labelQueryWrapper = new LambdaQueryWrapper<>();
labelQueryWrapper.eq(CaseClinicalArticleLabelEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleLabelEntity> caseClinicalArticleLabels = caseClinicalArticleLabelDao.selectList(labelQueryWrapper);
for (CaseClinicalArticleLabelEntity label : caseClinicalArticleLabels){
// 减少标签统计
caseClinicalService.DecStatsCaseClinicalLabel(label.getAppIden(),1);
}
// 获取作者数据
LambdaQueryWrapper<CaseClinicalArticleAuthorEntity> authorQueryWrapper = new LambdaQueryWrapper<>();
authorQueryWrapper.eq(CaseClinicalArticleAuthorEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthors = caseClinicalArticleAuthorDao.selectList(authorQueryWrapper);
for (CaseClinicalArticleAuthorEntity author : caseClinicalArticleAuthors){
// 获取医生数据
LambdaQueryWrapper<CaseClinicalDoctorEntity> doctorQueryWrapper = new LambdaQueryWrapper<>();
doctorQueryWrapper.eq(CaseClinicalDoctorEntity::getDoctorId,author.getDoctorId());
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectOne(doctorQueryWrapper);
if (caseClinicalDoctor == null) {
throw new BusinessException("无法完成此操作");
}
// 减少医生统计
caseClinicalService.DecStatsCaseClinicalDoctor(String.valueOf(author.getDoctorId()),1);
// 减少医院统计
caseClinicalService.DecStatsCaseClinicalHospital(String.valueOf(caseClinicalDoctor.getHospitalId()),1);
}
return ResponseDTO.ok();
}
/**
* 生成文章分享二维码
*/
public byte[] addUnlimitedQrcode(Long articleId) {
try {
// 检查文章是否存在
CaseClinicalArticleEntity article = caseClinicalArticleDao.selectById(articleId);
if (article == null) {
throw new BusinessException("文章不存在");
}
// 生成二维码参数
String scene = "?id=" + articleId + "&type=1";
String page = "pages/detail/detail";
// 生成二维码
byte[] qrcodeBytes = wxMaServiceUtils.getUnlimitedQrcode(scene, page);
// 生成文件名
String fileName;
String no = "a" + articleId;
// 根据环境设置路径
String envPath = Objects.equals(activeProfile, "dev") ? "dev/" : "prod/";
fileName = envPath + "static/images/" + no + ".png";
// 上传到OSS
boolean res = Oss.putObject(fileName, qrcodeBytes);
if (!res) {
throw new BusinessException("上传二维码失败");
}
// 更新文章记录
article.setShareQrcode("/" + fileName);
caseClinicalArticleDao.updateById(article);
return qrcodeBytes;
} catch (Exception e) {
throw new BusinessException(e.getMessage());
}
}
/**
* 获取文章详情
*/
public ResponseDTO<CaseClinicalArticleVO> getDetail(Long articleId) {
CaseClinicalArticleEntity entity = caseClinicalArticleDao.selectById(articleId);
if (entity == null) {
return ResponseDTO.userErrorParam("文章不存在");
}
CaseClinicalArticleVO vo = SmartBeanUtil.copy(entity, CaseClinicalArticleVO.class);
// 获取作者数据
LambdaQueryWrapper<CaseClinicalArticleAuthorEntity> authorQueryWrapper = new LambdaQueryWrapper<>();
authorQueryWrapper.eq(CaseClinicalArticleAuthorEntity::getArticleId, articleId);
List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthors = caseClinicalArticleAuthorDao.selectList(authorQueryWrapper);
List<CaseClinicalArticleAuthorVO> caseClinicalArticleAuthorVOs = SmartBeanUtil.copyList(caseClinicalArticleAuthors, CaseClinicalArticleAuthorVO.class);
// 为每个作者VO设置医生数据
for (CaseClinicalArticleAuthorVO authorVO : caseClinicalArticleAuthorVOs) {
// 查询医生
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectById(authorVO.getDoctorId());
if (caseClinicalDoctor != null) {
// 查询医生所属医院
BasicHospitalEntity basicHospital = basicHospitalDao.selectById(caseClinicalDoctor.getHospitalId());
if (basicHospital != null) {
caseClinicalDoctor.setBasicHospital(basicHospital);
}
// 转换为VO并设置医院信息
CaseClinicalDoctorVO caseClinicalDoctorVO = SmartBeanUtil.copy(caseClinicalDoctor, CaseClinicalDoctorVO.class);
if (basicHospital != null) {
caseClinicalDoctorVO.setHospitalName(basicHospital.getHospitalName());
caseClinicalDoctorVO.setHospitalProvince(basicHospital.getProvince());
caseClinicalDoctorVO.setHospitalCity(basicHospital.getCity());
}
authorVO.setCaseClinicalDoctor(caseClinicalDoctorVO);
}
}
vo.setCaseClinicalArticleAuthor(caseClinicalArticleAuthorVOs);
// 获取标签数据
LambdaQueryWrapper<CaseClinicalArticleLabelEntity> labelQueryWrapper = new LambdaQueryWrapper<>();
labelQueryWrapper.eq(CaseClinicalArticleLabelEntity::getArticleId, articleId);
List<CaseClinicalArticleLabelEntity> caseClinicalArticleLabels = caseClinicalArticleLabelDao.selectList(labelQueryWrapper);
// 转换为VO
List<CaseClinicalArticleLabelVO> caseClinicalArticleLabelVOs = SmartBeanUtil.copyList(caseClinicalArticleLabels, CaseClinicalArticleLabelVO.class);
vo.setCaseClinicalArticleLabel(caseClinicalArticleLabelVOs);
vo.setShareQrcode(Replace.addOssDomain(vo.getShareQrcode()));
return ResponseDTO.ok(vo);
}
/**
* 更新文章状态
*/
public ResponseDTO<String> updateStatus(Long articleId, Integer status) {
if (null == articleId) {
return ResponseDTO.userErrorParam("文章ID不能为空");
}
CaseClinicalArticleEntity caseClinicalArticle = caseClinicalArticleDao.selectById(articleId);
if (caseClinicalArticle == null) {
return ResponseDTO.userErrorParam("文章不存在");
}
// 如果状态没有变化直接返回
if (Objects.equals(caseClinicalArticle.getArticleStatus(), status)) {
return ResponseDTO.ok();
}
// 更新状态
caseClinicalArticle.setArticleStatus(status);
caseClinicalArticleDao.updateById(caseClinicalArticle);
// 如果状态发生改变修正统计数据
if (status == 2) {
// 状态改为禁用减少统计数量
DecClinicalArticleStats(String.valueOf(caseClinicalArticle.getArticleId()), 4, 1);
// 获取标签数据
LambdaQueryWrapper<CaseClinicalArticleLabelEntity> labelQueryWrapper = new LambdaQueryWrapper<>();
labelQueryWrapper.eq(CaseClinicalArticleLabelEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleLabelEntity> caseClinicalArticleLabels = caseClinicalArticleLabelDao.selectList(labelQueryWrapper);
for (CaseClinicalArticleLabelEntity label : caseClinicalArticleLabels) {
// 减少标签统计
caseClinicalService.DecStatsCaseClinicalLabel(label.getAppIden(), 1);
}
// 获取作者数据
LambdaQueryWrapper<CaseClinicalArticleAuthorEntity> authorQueryWrapper = new LambdaQueryWrapper<>();
authorQueryWrapper.eq(CaseClinicalArticleAuthorEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthors = caseClinicalArticleAuthorDao.selectList(authorQueryWrapper);
for (CaseClinicalArticleAuthorEntity author : caseClinicalArticleAuthors) {
// 获取医生数据
LambdaQueryWrapper<CaseClinicalDoctorEntity> doctorQueryWrapper = new LambdaQueryWrapper<>();
doctorQueryWrapper.eq(CaseClinicalDoctorEntity::getDoctorId, author.getDoctorId());
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectOne(doctorQueryWrapper);
if (caseClinicalDoctor == null) {
throw new BusinessException("无法完成此操作");
}
// 减少医生统计
caseClinicalService.DecStatsCaseClinicalDoctor(String.valueOf(author.getDoctorId()), 1);
// 减少医院统计
caseClinicalService.DecStatsCaseClinicalHospital(String.valueOf(caseClinicalDoctor.getHospitalId()), 1);
}
} else if (status == 1) {
// 状态改为正常增加统计数量
IncClinicalArticleStats(String.valueOf(caseClinicalArticle.getArticleId()), 4, 1);
// 获取标签数据
LambdaQueryWrapper<CaseClinicalArticleLabelEntity> labelQueryWrapper = new LambdaQueryWrapper<>();
labelQueryWrapper.eq(CaseClinicalArticleLabelEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleLabelEntity> caseClinicalArticleLabels = caseClinicalArticleLabelDao.selectList(labelQueryWrapper);
for (CaseClinicalArticleLabelEntity label : caseClinicalArticleLabels) {
// 增加标签统计
LocalDateTime lastPushDate = caseClinicalArticleDao.selectLastArticlePushDateByLabelId(label.getAppIden());
caseClinicalService.IncStatsCaseClinicalLabel(label.getAppIden(), label.getLabelName(), 1, lastPushDate);
}
// 获取作者数据
LambdaQueryWrapper<CaseClinicalArticleAuthorEntity> authorQueryWrapper = new LambdaQueryWrapper<>();
authorQueryWrapper.eq(CaseClinicalArticleAuthorEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthors = caseClinicalArticleAuthorDao.selectList(authorQueryWrapper);
for (CaseClinicalArticleAuthorEntity author : caseClinicalArticleAuthors) {
// 获取医生数据
LambdaQueryWrapper<CaseClinicalDoctorEntity> doctorQueryWrapper = new LambdaQueryWrapper<>();
doctorQueryWrapper.eq(CaseClinicalDoctorEntity::getDoctorId, author.getDoctorId());
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectOne(doctorQueryWrapper);
if (caseClinicalDoctor == null) {
throw new BusinessException("无法完成此操作");
}
// 增加医生统计
LocalDateTime lastPushDate = caseClinicalArticleDao.selectLastArticlePushDateByDoctorId(caseClinicalDoctor.getDoctorId());
caseClinicalService.IncStatsCaseClinicalDoctor(String.valueOf(author.getDoctorId()), 1, lastPushDate);
// 增加医院统计
caseClinicalService.IncStatsCaseClinicalHospital(String.valueOf(caseClinicalDoctor.getHospitalId()), 1, lastPushDate);
}
}
return ResponseDTO.ok();
}
/**
* 新增文章的统计字段
* @param articleId 文章id
* @param type 类型1:阅读量 2收藏量 3:评论数 4:文章数
*/
public boolean IncClinicalArticleStats(String articleId,Integer type,Integer num){
try {
String caseClinicalArticleField = ""; // 具体文章
String statsCaseClinicalField = ""; // 全部文章
// 阅读
if (type == 1){
caseClinicalArticleField = "read_num"; // 具体文章
statsCaseClinicalField = "article_read_num"; // 全部文章
}
// 收藏
if (type == 2){
caseClinicalArticleField = "collect_num"; // 具体文章
statsCaseClinicalField = "article_collect_num"; // 全部文章
}
// 评论
if (type == 3){
caseClinicalArticleField = "comment_num"; // 具体文章
statsCaseClinicalField = "article_comment_num"; // 全部文章
}
// 文章数
if (type == 4){
statsCaseClinicalField = "article_num"; // 全部文章
}
if (!caseClinicalArticleField.isEmpty()){
caseClinicalArticleDao.inc(Long.valueOf(articleId),caseClinicalArticleField,num);
}
statsCaseClinicalDao.inc(1L,statsCaseClinicalField,num);
return true;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
}
/**
* 减少文章的统计字段
* @param articleId 文章id
* @param type 类型1:阅读量 2收藏量 3:评论数 4:文章数
*/
public boolean DecClinicalArticleStats(String articleId,Integer type,Integer num){
try {
String caseClinicalArticleField = ""; // 具体文章
String statsCaseClinicalField = ""; // 全部文章
// 阅读
if (type == 1){
caseClinicalArticleField = "read_num"; // 具体文章
statsCaseClinicalField = "article_read_num"; // 全部文章
}
// 收藏
if (type == 2){
caseClinicalArticleField = "collect_num"; // 具体文章
statsCaseClinicalField = "article_collect_num"; // 全部文章
}
// 评论
if (type == 3){
caseClinicalArticleField = "comment_num"; // 具体文章
statsCaseClinicalField = "article_comment_num"; // 全部文章
}
// 文章数
if (type == 4){
statsCaseClinicalField = "article_num"; // 全部文章
}
if (!caseClinicalArticleField.isEmpty()){
caseClinicalArticleDao.dec(Long.valueOf(articleId),caseClinicalArticleField,num);
}
statsCaseClinicalDao.dec(1L,statsCaseClinicalField,num);
return true;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
}
// 处理文章作者
public void HandleArticleAuthor(CaseClinicalArticleEntity caseClinicalArticle,List<CaseClinicalArticleAuthorForm> r,byte[] qrCodeBytes){
// 需新增的
List<CaseClinicalArticleAuthorForm> addList = new ArrayList<>();
// 需删除的
List<CaseClinicalArticleAuthorEntity> deleteList = new ArrayList<>();
// 获取全部作者
LambdaQueryWrapper<CaseClinicalArticleAuthorEntity> authorQueryWrapper = new LambdaQueryWrapper<>();
authorQueryWrapper.eq(CaseClinicalArticleAuthorEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleAuthorEntity> caseClinicalArticleAuthors = caseClinicalArticleAuthorDao.selectList(authorQueryWrapper);
// 处理新增的情况
for (CaseClinicalArticleAuthorForm a : r){
// 默认本条为新增
boolean exists = true;
for (CaseClinicalArticleAuthorEntity b : caseClinicalArticleAuthors){
if (Objects.equals(a.getDoctorId(), b.getDoctorId())){
exists = false;
break; // 已存在跳出内层循环
}
}
if (exists) {
addList.add(a); // 不存在于旧数据中加入新增列表
}
}
// 处理删除的情况
for (CaseClinicalArticleAuthorEntity b : caseClinicalArticleAuthors){
// 默认本条为删除
boolean exists = true;
for (CaseClinicalArticleAuthorForm a : r){
if (Objects.equals(a.getDoctorId(), b.getDoctorId())){
exists = false;
break; // 已存在跳出内层循环
}
}
if (exists) {
deleteList.add(b); // 加入删除列表
}
}
// 删除
for (CaseClinicalArticleAuthorEntity author : deleteList){
// 获取医生数据
LambdaQueryWrapper<CaseClinicalDoctorEntity> doctorQueryWrapper = new LambdaQueryWrapper<>();
doctorQueryWrapper.eq(CaseClinicalDoctorEntity::getDoctorId, author.getDoctorId());
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectOne(doctorQueryWrapper);
if (caseClinicalDoctor == null) {
throw new BusinessException("操作失败");
}
// 获取医院数据
BasicHospitalEntity basicHospital = basicHospitalDao.selectById(caseClinicalDoctor.getHospitalId());
if (basicHospital == null) {
throw new BusinessException("操作失败");
}
// 减少作者统计
caseClinicalService.DecStatsCaseClinicalDoctor(String.valueOf(caseClinicalDoctor.getDoctorId()),1);
// 减少医院统计
caseClinicalService.DecStatsCaseClinicalHospital(String.valueOf(caseClinicalDoctor.getHospitalId()),1);
// 删除该作者
caseClinicalArticleAuthorDao.deleteById(author.getAuthorId());
// 删除该作者证书
LambdaQueryWrapper<CaseClinicalDoctorCertEntity> certWrapper = new LambdaQueryWrapper<>();
certWrapper.eq(CaseClinicalDoctorCertEntity::getDoctorId, caseClinicalDoctor.getDoctorId());
certWrapper.eq(CaseClinicalDoctorCertEntity::getId, caseClinicalArticle.getArticleId());
certWrapper.eq(CaseClinicalDoctorCertEntity::getType, 1);
caseClinicalDoctorCertDao.delete(certWrapper);
}
// 新增新的作者
for (CaseClinicalArticleAuthorForm author : addList){
CaseClinicalDoctorEntity caseClinicalDoctor = caseClinicalDoctorDao.selectById(author.getDoctorId());
CaseClinicalArticleAuthorEntity caseClinicalArticleAuthor = new CaseClinicalArticleAuthorEntity();
caseClinicalArticleAuthor.setArticleId(caseClinicalArticle.getArticleId());
caseClinicalArticleAuthor.setDoctorId(caseClinicalDoctor.getDoctorId());
caseClinicalArticleAuthorDao.insert(caseClinicalArticleAuthor);
LocalDateTime lastPushDate = caseClinicalArticleDao.selectLastArticlePushDateByDoctorId(caseClinicalDoctor.getDoctorId());
// 新增作者统计
caseClinicalService.IncStatsCaseClinicalDoctor(String.valueOf(caseClinicalDoctor.getDoctorId()),1,lastPushDate);
// 新增医院统计
caseClinicalService.IncStatsCaseClinicalHospital(String.valueOf(caseClinicalDoctor.getHospitalId()),1,lastPushDate);
// 生成用户证书-文章/视频
if (qrCodeBytes == null || qrCodeBytes.length == 0) {
// 生成二维码图片
if (caseClinicalArticle.getShareQrcode() == null){
try {
// 生成用户分享二维码-文章/视频
qrCodeBytes = addUnlimitedQrcode(caseClinicalArticle.getArticleId());
} catch (Exception e) {
// 不处理
throw new BusinessException("操作失败");
}
}else{
// 下载二维码图片
qrCodeBytes = Oss.getObjectToByte(caseClinicalArticle.getShareQrcode().replaceFirst("^/+", ""));
if (qrCodeBytes == null) {
throw new BusinessException("操作失败");
}
}
}
// 下载头像
byte[] avatarByte = new byte[0];
if (caseClinicalDoctor.getAvatar() != null && !caseClinicalDoctor.getAvatar().isEmpty()) {
avatarByte = Oss.getObjectToByte(caseClinicalDoctor.getAvatar().replaceFirst("^/+", ""));
}else{
try {
avatarByte = ImageUtil.readImageToBytes("static/cert/avatar.png");
} catch (Exception e) {
throw new BusinessException(e.getMessage());
}
}
if (avatarByte == null) {
throw new BusinessException("操作失败");
}
userService.CreateUserCert(
String.valueOf(caseClinicalArticle.getArticleId()),
1,
String.valueOf(caseClinicalDoctor.getDoctorId()),
qrCodeBytes,
avatarByte
);
}
}
// 处理文章标签
public void HandleArticleLabel(CaseClinicalArticleEntity caseClinicalArticle, List<CaseClinicalArticleLabelForm> r){
// 获取全部b标签
LambdaQueryWrapper<CaseClinicalArticleLabelEntity> labelQueryWrapper = new LambdaQueryWrapper<>();
labelQueryWrapper.eq(CaseClinicalArticleLabelEntity::getArticleId, caseClinicalArticle.getArticleId());
List<CaseClinicalArticleLabelEntity> caseClinicalArticleLabels = caseClinicalArticleLabelDao.selectList(labelQueryWrapper);
for (CaseClinicalArticleLabelEntity label : caseClinicalArticleLabels){
// 减少标签统计
caseClinicalService.DecStatsCaseClinicalLabel(label.getAppIden(),1);
// 删除视频标签
caseClinicalArticleLabelDao.deleteById(label.getArticleLabelId());
}
for (CaseClinicalArticleLabelForm label : r){
CaseClinicalArticleLabelEntity caseClinicalArticleLabel = new CaseClinicalArticleLabelEntity();
caseClinicalArticleLabel.setArticleId(caseClinicalArticle.getArticleId());
caseClinicalArticleLabel.setAppIden(label.getAppIden());
caseClinicalArticleLabel.setLabelName(label.getLabelName());
caseClinicalArticleLabelDao.insert(caseClinicalArticleLabel);
LocalDateTime lastPushDate = caseClinicalArticleDao.selectLastArticlePushDateByLabelId(label.getAppIden());
// 新增标签统计
caseClinicalService.IncStatsCaseClinicalLabel(label.getAppIden(),caseClinicalArticleLabel.getLabelName(),1,lastPushDate);
}
}
}

View File

@ -0,0 +1,40 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.controller;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.form.CaseClinicalArticleAuthorQueryForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.vo.CaseClinicalArticleAuthorVO;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.service.CaseClinicalArticleAuthorService;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
/**
* 病例库-临床-文章-作者 Controller
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@RestController
@Tag(name = "病例库-临床-文章-作者")
public class CaseClinicalArticleAuthorController {
@Resource
private CaseClinicalArticleAuthorService caseClinicalArticleAuthorService;
@Operation(summary = "分页查询 @author xing")
@PostMapping("/caseClinicalArticleAuthor/queryPage")
@SaCheckPermission("caseClinicalArticleAuthor:query")
public ResponseDTO<PageResult<CaseClinicalArticleAuthorVO>> queryPage(@RequestBody @Valid CaseClinicalArticleAuthorQueryForm queryForm) {
return ResponseDTO.ok(caseClinicalArticleAuthorService.queryPage(queryForm));
}
}

View File

@ -0,0 +1,33 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.dao;
import java.util.List;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.entity.CaseClinicalArticleAuthorEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.form.CaseClinicalArticleAuthorQueryForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.vo.CaseClinicalArticleAuthorVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
/**
* 病例库-临床-文章-作者 Dao
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@Mapper
public interface CaseClinicalArticleAuthorDao extends BaseMapper<CaseClinicalArticleAuthorEntity> {
/**
* 分页 查询
*
* @param page
* @param queryForm
* @return
*/
List<CaseClinicalArticleAuthorVO> queryPage(Page page, @Param("queryForm") CaseClinicalArticleAuthorQueryForm queryForm);
}

View File

@ -0,0 +1,56 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.entity.CaseClinicalDoctorEntity;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo.CaseClinicalDoctorVO;
/**
* 病例库-临床-文章-作者 实体类
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@Data
@TableName("case_clinical_article_author")
public class CaseClinicalArticleAuthorEntity {
/**
* 主键id
*/
@TableId
private Long authorId;
/**
* 临床文章id
*/
private Long articleId;
/**
* 医生id
*/
private Long doctorId;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
/**
* 医生数据
*/
@TableField(exist = false)
private CaseClinicalDoctorEntity caseClinicalDoctor;
}

View File

@ -0,0 +1,19 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.form;
import net.lab1024.sa.base.common.domain.PageParam;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 病例库-临床-文章-作者 分页查询表单
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class CaseClinicalArticleAuthorQueryForm extends PageParam {
}

View File

@ -0,0 +1,42 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.vo;
import cn.hutool.core.bean.BeanUtil;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Data;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.entity.CaseClinicalArticleAuthorEntity;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo.CaseClinicalDoctorVO;
/**
* 病例库-临床-文章-作者 列表VO
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@Data
public class CaseClinicalArticleAuthorVO {
@Schema(description = "主键id")
private Long authorId;
@Schema(description = "临床文章id")
private Long articleId;
@Schema(description = "医生id")
private Long doctorId;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "修改时间")
private LocalDateTime updatedAt;
@Schema(description = "医生数据")
private CaseClinicalDoctorVO caseClinicalDoctor;
}

View File

@ -0,0 +1,20 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.manager;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.entity.CaseClinicalArticleAuthorEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.dao.CaseClinicalArticleAuthorDao;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* 病例库-临床-文章-作者 Manager
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@Service
public class CaseClinicalArticleAuthorManager extends ServiceImpl<CaseClinicalArticleAuthorDao, CaseClinicalArticleAuthorEntity> {
}

View File

@ -0,0 +1,42 @@
package net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.service;
import java.util.List;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.dao.CaseClinicalArticleAuthorDao;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.entity.CaseClinicalArticleAuthorEntity;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.form.CaseClinicalArticleAuthorQueryForm;
import net.lab1024.sa.admin.module.business.caseClinicalArticleAuthor.domain.vo.CaseClinicalArticleAuthorVO;
import net.lab1024.sa.base.common.util.SmartBeanUtil;
import net.lab1024.sa.base.common.util.SmartPageUtil;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
/**
* 病例库-临床-文章-作者 Service
*
* @Author xing
* @Date 2025-08-05 08:45:33
* @Copyright gdxz
*/
@Service
public class CaseClinicalArticleAuthorService {
@Resource
private CaseClinicalArticleAuthorDao caseClinicalArticleAuthorDao;
/**
* 分页查询
*/
public PageResult<CaseClinicalArticleAuthorVO> queryPage(CaseClinicalArticleAuthorQueryForm queryForm) {
Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
List<CaseClinicalArticleAuthorVO> list = caseClinicalArticleAuthorDao.queryPage(page, queryForm);
return SmartPageUtil.convert2PageResult(page, list);
}
}

View File

@ -0,0 +1,105 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.controller;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form.CaseClinicalDoctorAddForm;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form.CaseClinicalDoctorQueryForm;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form.CaseClinicalDoctorQueryListForm;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form.CaseClinicalDoctorUpdateForm;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form.CaseClinicalDoctorStatusUpdateForm;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo.CaseClinicalDoctorVO;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo.AppDoctorVO;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.service.CaseClinicalDoctorService;
import net.lab1024.sa.base.common.domain.ValidateList;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.List;
/**
* 医生管理 Controller
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@RestController
@Tag(name = "医生管理")
public class CaseClinicalDoctorController {
@Resource
private CaseClinicalDoctorService caseClinicalDoctorService;
@Operation(summary = "分页查询 @author xing")
@PostMapping("/caseClinicalDoctor/queryPage")
@SaCheckPermission("caseClinicalDoctor:query")
public ResponseDTO<PageResult<CaseClinicalDoctorVO>> queryPage(@RequestBody @Valid CaseClinicalDoctorQueryForm queryForm) {
return ResponseDTO.ok(caseClinicalDoctorService.queryPage(queryForm));
}
@Operation(summary = "列表查询 @author xing")
@PostMapping("/caseClinicalDoctor/queryList")
@SaCheckPermission("caseClinicalDoctor:query")
public ResponseDTO<List<CaseClinicalDoctorVO>> queryList(@RequestBody @Valid CaseClinicalDoctorQueryListForm queryForm) {
return ResponseDTO.ok(caseClinicalDoctorService.queryList(queryForm));
}
@Operation(summary = "添加 @author xing")
@PostMapping("/caseClinicalDoctor/add")
@SaCheckPermission("caseClinicalDoctor:add")
public ResponseDTO<String> add(@RequestBody @Valid CaseClinicalDoctorAddForm addForm) {
return caseClinicalDoctorService.add(addForm);
}
@Operation(summary = "更新 @author xing")
@PostMapping("/caseClinicalDoctor/update")
@SaCheckPermission("caseClinicalDoctor:update")
public ResponseDTO<String> update(@RequestBody @Valid CaseClinicalDoctorUpdateForm updateForm) {
return caseClinicalDoctorService.update(updateForm);
}
@Operation(summary = "批量删除 @author xing")
@PostMapping("/caseClinicalDoctor/batchDelete")
@SaCheckPermission("caseClinicalDoctor:delete")
public ResponseDTO<String> batchDelete(@RequestBody ValidateList<Long> idList) {
return caseClinicalDoctorService.batchDelete(idList);
}
@Operation(summary = "单个删除 @author xing")
@GetMapping("/caseClinicalDoctor/delete/{doctorId}")
@SaCheckPermission("caseClinicalDoctor:delete")
public ResponseDTO<String> batchDelete(@PathVariable Long doctorId) {
return caseClinicalDoctorService.delete(doctorId);
}
@Operation(summary = "更新状态 @author xing")
@PostMapping("/caseClinicalDoctor/status/update")
@SaCheckPermission("caseClinicalDoctor:update")
public ResponseDTO<String> updateStatus(@RequestBody @Valid CaseClinicalDoctorStatusUpdateForm updateForm) {
return caseClinicalDoctorService.updateStatus(updateForm.getDoctorId(), updateForm.getStatus());
}
@Operation(summary = "获取APP医生信息 @author xing")
@GetMapping("/caseClinicalDoctor/getAppDoctor")
@SaCheckPermission("caseClinicalDoctor:query")
public ResponseDTO<AppDoctorVO> getAppDoctor(@RequestParam String doctorName) {
return caseClinicalDoctorService.getAppDoctor(doctorName);
}
@Operation(summary = "获取医生详情 @author xing")
@GetMapping("/caseClinicalDoctor/getDetail/{doctorId}")
@SaCheckPermission("caseClinicalDoctor:query")
public ResponseDTO<CaseClinicalDoctorVO> getDetail(@PathVariable Long doctorId) {
return caseClinicalDoctorService.getDetail(doctorId);
}
}

View File

@ -0,0 +1,24 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.dao;
import java.util.List;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.entity.CaseClinicalDoctorCertEntity;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo.CaseClinicalDoctorCertVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
/**
* 医生证书 Dao
*
* @Author xing
* @Date 2025-08-06 11:22:22
* @Copyright gdxz
*/
@Mapper
public interface CaseClinicalDoctorCertDao extends BaseMapper<CaseClinicalDoctorCertEntity> {
}

View File

@ -0,0 +1,42 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.dao;
import java.util.List;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.entity.CaseClinicalDoctorEntity;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form.CaseClinicalDoctorQueryForm;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form.CaseClinicalDoctorQueryListForm;
import net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo.CaseClinicalDoctorVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;
/**
* 医生管理 Dao
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@Mapper
public interface CaseClinicalDoctorDao extends BaseMapper<CaseClinicalDoctorEntity> {
/**
* 分页 查询
*
* @param page
* @param queryForm
* @return
*/
List<CaseClinicalDoctorVO> queryPage(Page page, @Param("queryForm") CaseClinicalDoctorQueryForm queryForm);
/**
* 列表 查询
*
* @param queryForm
* @return
*/
List<CaseClinicalDoctorVO> queryList(@Param("queryForm") CaseClinicalDoctorQueryListForm queryForm);
}

View File

@ -0,0 +1,56 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 医生证书 实体类
*
* @Author xing
* @Date 2025-08-06 11:22:22
* @Copyright gdxz
*/
@Data
@TableName("case_clinical_doctor_cert")
public class CaseClinicalDoctorCertEntity {
/**
* 主键id
*/
@TableId
private Long certId;
/**
* 医生id
*/
private Long doctorId;
/**
* 关联的视频/文章id
*/
private Long id;
/**
* 类型1:文章 2:视频
*/
private Integer type;
/**
* 证书图片
*/
private String certImage;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
}

View File

@ -0,0 +1,69 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import lombok.Data;
import net.lab1024.sa.admin.module.business.basicHospital.domain.entity.BasicHospitalEntity;
/**
* 医生管理 实体类
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@Data
@TableName("case_clinical_doctor")
public class CaseClinicalDoctorEntity {
/**
* 主键id
*/
@TableId
private Long doctorId;
/**
* app唯一标识(存在即表示为注册用户)
*/
private String doctorIden;
/**
* 用户名称
*/
private String doctorName;
/**
* 所属医院id
*/
private Long hospitalId;
/**
* 状态1:正常 2:禁用
*/
private Integer status;
/**
* 头像
*/
private String avatar;
/**
* 创建时间
*/
private LocalDateTime createdAt;
/**
* 修改时间
*/
private LocalDateTime updatedAt;
/**
* 医院数据
*/
@TableField(exist = false)
private BasicHospitalEntity basicHospital;
}

View File

@ -0,0 +1,40 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer;
/**
* 医生管理 新建表单
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@Data
public class CaseClinicalDoctorAddForm {
@Schema(description = "用户名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "用户名称 不能为空")
private String doctorName;
@Schema(description = "app唯一标识(可用uuid入参)")
@JsonAlias({"uuid"})
private String doctorIden;
@Schema(description = "所属医院id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "所属医院id 不能为空")
private Long hospitalId;
@Schema(description = "医院UUID")
private String hospitalUuid;
@Schema(description = "状态1:正常 2:禁用)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "状态1:正常 2:禁用) 不能为空")
private Integer status = 1;
}

View File

@ -0,0 +1,29 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form;
import net.lab1024.sa.base.common.domain.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 医生管理 分页查询表单
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class CaseClinicalDoctorQueryForm extends PageParam {
@Schema(description = "医生姓名")
private String keywords;
@Schema(description = "医院名称")
private String hospitalName;
@Schema(description = "状态")
private Integer status;
}

View File

@ -0,0 +1,23 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import net.lab1024.sa.base.common.domain.PageParam;
/**
* 医生管理 分页查询表单
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@Data
public class CaseClinicalDoctorQueryListForm {
@Schema(description = "医生姓名")
private String keywords;
@Schema(description = "数量")
private Integer limit = 10;
}

View File

@ -0,0 +1,26 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
/**
* 医生管理 状态更新表单
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@Data
@Schema(description = "医生管理 状态更新表单")
public class CaseClinicalDoctorStatusUpdateForm {
@Schema(description = "医生ID")
@NotNull(message = "医生ID不能为空")
private Long doctorId;
@Schema(description = "状态 1:正常 2:禁用")
@NotNull(message = "状态不能为空")
private Integer status;
}

View File

@ -0,0 +1,37 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.form;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer;
/**
* 医生管理 更新表单
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@Data
public class CaseClinicalDoctorUpdateForm {
@Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "主键id 不能为空")
private Long doctorId;
@Schema(description = "用户名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "用户名称 不能为空")
private String doctorName;
@Schema(description = "状态1:正常 2:禁用)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "状态1:正常 2:禁用) 不能为空")
private Integer status;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonDeserialize(using = FileKeyVoDeserializer.class)
private String avatar;
}

View File

@ -0,0 +1,37 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* APP医生信息 VO
*
* @Author xing
* @Date 2025-08-04 15:24:56
* @Copyright gdxz
*/
@Data
@Schema(description = "APP医生信息")
public class AppDoctorVO {
@Schema(description = "UUID")
private String uuid;
@Schema(description = "姓名")
private String doctorName;
@Schema(description = "手机号")
private String phone;
@Schema(description = "省份")
private String province;
@Schema(description = "医院UUID")
private String hospitalUuid;
@Schema(description = "医院ID")
private Long hospitalId;
@Schema(description = "医院名称")
private String hospitalName;
}

View File

@ -0,0 +1,40 @@
package net.lab1024.sa.admin.module.business.caseClinicalDoctor.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
import lombok.Data;
/**
* 医生证书 列表VO
*
* @Author xing
* @Date 2025-08-06 11:22:22
* @Copyright gdxz
*/
@Data
public class CaseClinicalDoctorCertVO {
@Schema(description = "主键id")
private Long certId;
@Schema(description = "医生id")
private Long doctorId;
@Schema(description = "关联的视频/文章id")
private Long id;
@Schema(description = "类型1:文章 2:视频)")
private Integer type;
@Schema(description = "证书图片")
private String certImage;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "修改时间")
private LocalDateTime updatedAt;
}

Some files were not shown because too many files have changed in this diff Show More