فهرست منبع

第一次提交

mcbaiyun 2 ماه پیش
کامیت
16beebbea6
38فایلهای تغییر یافته به همراه1140 افزوده شده و 0 حذف شده
  1. 31 0
      .gitignore
  2. 3 0
      .vscode/settings.json
  3. 98 0
      pom.xml
  4. 15 0
      src/main/java/work/baiyun/chronicdiseaseapp/SpringAPP.java
  5. 23 0
      src/main/java/work/baiyun/chronicdiseaseapp/common/Page.java
  6. 44 0
      src/main/java/work/baiyun/chronicdiseaseapp/common/R.java
  7. 20 0
      src/main/java/work/baiyun/chronicdiseaseapp/config/CorsConfig.java
  8. 38 0
      src/main/java/work/baiyun/chronicdiseaseapp/config/Knife4jConfig.java
  9. 16 0
      src/main/java/work/baiyun/chronicdiseaseapp/config/MybatisPlusConfig.java
  10. 27 0
      src/main/java/work/baiyun/chronicdiseaseapp/config/WeChatProperties.java
  11. 41 0
      src/main/java/work/baiyun/chronicdiseaseapp/controller/WeChatController.java
  12. 21 0
      src/main/java/work/baiyun/chronicdiseaseapp/enums/ExceptionResultCode.java
  13. 24 0
      src/main/java/work/baiyun/chronicdiseaseapp/enums/FailResultCode.java
  14. 37 0
      src/main/java/work/baiyun/chronicdiseaseapp/enums/PermissionGroup.java
  15. 37 0
      src/main/java/work/baiyun/chronicdiseaseapp/enums/SuccessResultCode.java
  16. 6 0
      src/main/java/work/baiyun/chronicdiseaseapp/exception/CustomException.java
  17. 76 0
      src/main/java/work/baiyun/chronicdiseaseapp/exception/CustomExceptionHandler.java
  18. 37 0
      src/main/java/work/baiyun/chronicdiseaseapp/handler/CustomMetaObjectHandler.java
  19. 41 0
      src/main/java/work/baiyun/chronicdiseaseapp/handler/PermissionGroupTypeHandler.java
  20. 9 0
      src/main/java/work/baiyun/chronicdiseaseapp/mapper/SysUserMapper.java
  21. 9 0
      src/main/java/work/baiyun/chronicdiseaseapp/mapper/UserLoginMapper.java
  22. 10 0
      src/main/java/work/baiyun/chronicdiseaseapp/mapper/UserTokenMapper.java
  23. 11 0
      src/main/java/work/baiyun/chronicdiseaseapp/model/bo/UserLoginBo.java
  24. 55 0
      src/main/java/work/baiyun/chronicdiseaseapp/model/po/BaseEntity.java
  25. 46 0
      src/main/java/work/baiyun/chronicdiseaseapp/model/po/UserInfo.java
  26. 32 0
      src/main/java/work/baiyun/chronicdiseaseapp/model/po/UserToken.java
  27. 8 0
      src/main/java/work/baiyun/chronicdiseaseapp/model/vo/GetOpenidRequest.java
  28. 19 0
      src/main/java/work/baiyun/chronicdiseaseapp/service/TokenService.java
  29. 16 0
      src/main/java/work/baiyun/chronicdiseaseapp/service/UserLoginService.java
  30. 9 0
      src/main/java/work/baiyun/chronicdiseaseapp/service/UserService.java
  31. 10 0
      src/main/java/work/baiyun/chronicdiseaseapp/service/WeChatService.java
  32. 79 0
      src/main/java/work/baiyun/chronicdiseaseapp/service/impl/TokenServiceImpl.java
  33. 17 0
      src/main/java/work/baiyun/chronicdiseaseapp/service/impl/UserLoginServiceImpl.java
  34. 12 0
      src/main/java/work/baiyun/chronicdiseaseapp/service/impl/UserServiceImpl.java
  35. 51 0
      src/main/java/work/baiyun/chronicdiseaseapp/service/impl/WeChatServiceImpl.java
  36. 14 0
      src/main/java/work/baiyun/chronicdiseaseapp/util/TokenUtil.java
  37. 49 0
      src/main/resources/application.yml
  38. 49 0
      target/classes/application.yml

+ 31 - 0
.gitignore

@@ -0,0 +1,31 @@
+# 忽略.idea目录
+**/.idea/
+
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# Nignx pidfile
+nginx/logs/nginx.pid
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+

+ 3 - 0
.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+    "java.compile.nullAnalysis.mode": "automatic"
+}

+ 98 - 0
pom.xml

@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.example</groupId>
+    <artifactId>ChronicDiseaseApp</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <spring-boot.version>3.0.13</spring-boot.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+
+    <dependencies>
+        <!-- Spring Boot 核心依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!-- 参数校验依赖 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!-- MyBatis-Plus 依赖 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.5.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+            <version>3.5.3</version>
+        </dependency>
+
+        <!-- PageHelper 分页插件 -->
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+            <version>1.4.2</version>
+        </dependency>
+
+        <!-- Lombok 依赖 -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.24</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.22</version>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>5.1.49</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <!-- Deleted:knife4j-openapi3-spring-boot-starter -->
+            <!-- Updated to Jakarta EE compatible version -->
+            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+
+
+
+    </dependencies>
+</project>

+ 15 - 0
src/main/java/work/baiyun/chronicdiseaseapp/SpringAPP.java

@@ -0,0 +1,15 @@
+package work.baiyun.chronicdiseaseapp;
+
+import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
+
+@EnableKnife4j
+@SpringBootApplication(exclude = {ThymeleafAutoConfiguration.class})
+public class SpringAPP {
+    public static void main(String[] args) {
+        SpringApplication app = new SpringApplication(SpringAPP.class);
+        app.run(args);
+    }
+}

+ 23 - 0
src/main/java/work/baiyun/chronicdiseaseapp/common/Page.java

@@ -0,0 +1,23 @@
+package work.baiyun.chronicdiseaseapp.common;
+
+import com.github.pagehelper.PageInfo;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@Setter
+@Getter
+public class Page<T> {
+    private int pageNum;
+    private int pageSize;
+    private Long total;
+    private List<T> list;
+    public Page(int pageNum, int pageSize, List<T> list){
+        PageInfo pageInfo = new PageInfo(list);
+        this.pageNum = pageNum;
+        this.pageSize = pageSize;
+        this.list = list;
+        this.total = pageInfo.getTotal();
+    }
+}

+ 44 - 0
src/main/java/work/baiyun/chronicdiseaseapp/common/R.java

@@ -0,0 +1,44 @@
+package work.baiyun.chronicdiseaseapp.common;
+
+import lombok.Data;
+
+@Data
+public class R<T> {
+    private Integer code;
+    private String message;
+    private T data;
+
+    /**
+    * @param code
+    * @param message
+    * @return
+    */
+    public static R<Void> success(Integer code, String message){
+        R<Void> result = new R<>();
+        result.code = code;
+        result.message = message;
+        return result;
+    }
+
+    public static <T> R<T> success(Integer code, String message, T data){
+        R<T> result = new R<T>();
+        result.code = code;
+        result.message = message;
+        result.data = data;
+        return result;
+    }
+
+    public static R<Void> fail(Integer code, String message){
+        R<Void> result = new R<>();
+        result.code = code;
+        result.message = message;
+        return result;
+    }
+    public static <T> R<T> fail(Integer code, String message, T data){
+        R<T> result = new R<T>();
+        result.code = code;
+        result.message = message;
+        result.data = data;
+        return result;
+    }
+}

+ 20 - 0
src/main/java/work/baiyun/chronicdiseaseapp/config/CorsConfig.java

@@ -0,0 +1,20 @@
+package work.baiyun.chronicdiseaseapp.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CorsConfig implements WebMvcConfigurer {
+//    由于在Vite配置了反向代理,因此不需要配置跨域了
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/api/**") // 指定需要跨域的路径
+                .allowedOrigins("http://localhost:3000") // 允许的源
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的方法
+                .allowedHeaders("*") // 允许的头信息
+                .exposedHeaders("X-Custom-Header") // 暴露给前端的头部
+                .maxAge(3600) // 预检请求的有效期(单位:秒)
+                .allowCredentials(true); // 是否允许发送Cookie
+    }
+}

+ 38 - 0
src/main/java/work/baiyun/chronicdiseaseapp/config/Knife4jConfig.java

@@ -0,0 +1,38 @@
+package work.baiyun.chronicdiseaseapp.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class Knife4jConfig {
+    @Bean
+    public OpenAPI customOpenAPI() {
+        return new OpenAPI()
+                .info(new Info()
+                        .title("Verification API")
+                        .description("提供Verification服务的接口文档")
+                        .version("v0")
+//                        .termsOfService("None")
+                        .contact(new Contact()
+                                .name("baiyun")
+                                .url("https://baiyun.work/")
+                                .email("i@baiyun.work")))
+                .addServersItem(new Server()
+                        .url("http://localhost:8080")
+                        .description("本地开发环境"));
+    }
+
+    @Bean
+    public GroupedOpenApi publicApi() {
+        return GroupedOpenApi.builder()
+                .group("verification-api")
+                // Updated: 修改为当前项目的基础包路径
+                .packagesToScan("work.baiyun.verification")
+                .build();
+    }
+}

+ 16 - 0
src/main/java/work/baiyun/chronicdiseaseapp/config/MybatisPlusConfig.java

@@ -0,0 +1,16 @@
+package work.baiyun.chronicdiseaseapp.config;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+@Configuration
+public class MybatisPlusConfig {
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor(){
+        // 实例化拦截器链
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 添加乐观锁插件
+        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+        return interceptor;
+    }
+}

+ 27 - 0
src/main/java/work/baiyun/chronicdiseaseapp/config/WeChatProperties.java

@@ -0,0 +1,27 @@
+package work.baiyun.chronicdiseaseapp.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "wechat")
+public class WeChatProperties {
+    private String appid;
+    private String secret;
+
+    public String getAppid() {
+        return appid;
+    }
+
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+}

+ 41 - 0
src/main/java/work/baiyun/chronicdiseaseapp/controller/WeChatController.java

@@ -0,0 +1,41 @@
+package work.baiyun.chronicdiseaseapp.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import work.baiyun.chronicdiseaseapp.common.R;
+import work.baiyun.chronicdiseaseapp.model.vo.GetOpenidRequest;
+import work.baiyun.chronicdiseaseapp.service.WeChatService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+public class WeChatController {
+
+    @Autowired
+    private WeChatService weChatService;
+
+    @RequestMapping("/")
+    public R<?> hello() {
+        return R.success(200, "Hello from Spring backend!");
+    }
+
+    @PostMapping(path = "/get_openid", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
+    public R<?> getOpenid(@RequestBody(required = false) GetOpenidRequest req) {
+        if (req == null || req.getCode() == null || req.getCode().isEmpty()) {
+            return R.fail(400, "Missing code parameter");
+        }
+        String openid = weChatService.getOpenId(req.getCode());
+        if (openid != null) {
+            Map<String, String> data = new HashMap<>();
+            data.put("openid", openid);
+            return R.success(200, "ok", data);
+        } else {
+            return R.fail(500, "Failed to get openid");
+        }
+    }
+}

+ 21 - 0
src/main/java/work/baiyun/chronicdiseaseapp/enums/ExceptionResultCode.java

@@ -0,0 +1,21 @@
+package work.baiyun.chronicdiseaseapp.enums;
+
+public enum ExceptionResultCode {
+    VALID_EXCEPTION(400, "参数校验异常"),
+    EXCEPTION(400, "系统异常");
+
+    private final int code;
+    private final String message;
+
+    ExceptionResultCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public int getCode() { return code; }
+    public String getMessage() { return message; }
+
+    public String getMsg() {
+        return message;
+    }
+}

+ 24 - 0
src/main/java/work/baiyun/chronicdiseaseapp/enums/FailResultCode.java

@@ -0,0 +1,24 @@
+package work.baiyun.chronicdiseaseapp.enums;
+
+public enum FailResultCode {
+    USER_NOT_EXIST(300, "用户不存在"),
+    USER_PASSWORD_ERROR(301, "用户密码错误"),
+    USER_NOT_LOGIN(302, "用户未登录");
+
+    private final int code;
+    private final String message;
+
+    FailResultCode(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}
+

+ 37 - 0
src/main/java/work/baiyun/chronicdiseaseapp/enums/PermissionGroup.java

@@ -0,0 +1,37 @@
+package work.baiyun.chronicdiseaseapp.enums;
+
+import com.baomidou.mybatisplus.annotation.EnumValue;
+
+/**
+ * 权限组枚举:游客、患者、医生、系统管理员、患者家属
+ */
+public enum PermissionGroup {
+    SYS_ADMIN(1, "管理员"),
+    DOCTOR(2, "医生"),
+    PATIENT(3, "患者"),
+    PATIENT_FAMILY(4, "患者家属");
+    
+
+    @EnumValue
+    private final int code;
+    private final String description;
+
+    PermissionGroup(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public int getCode() { return code; }
+
+    public String getDescription() { return description; }
+
+    @Override
+    public String toString() { return description; }
+
+    public static PermissionGroup fromCode(int code) {
+        for (PermissionGroup g : PermissionGroup.values()) {
+            if (g.code == code) return g;
+        }
+        throw new IllegalArgumentException("Unknown PermissionGroup code: " + code);
+    }
+}

+ 37 - 0
src/main/java/work/baiyun/chronicdiseaseapp/enums/SuccessResultCode.java

@@ -0,0 +1,37 @@
+package work.baiyun.chronicdiseaseapp.enums;
+
+public enum SuccessResultCode {
+
+    /**
+     * *操作成功
+     */
+    SUCCESS( 200,"操作成功"),
+
+    /**
+     * *认证成功
+     */
+    AUTHENTICATION_SUCCESS( 200,"认证成功"),
+
+    /**
+     * *刷新Token成功
+     */
+    REFRESH_ACCESS_TOKEN_SUCCESS(200, "Token刷新成功"),
+
+    /**
+     * *退出登录成功
+     */
+    L0GOUT_SUCCESS( 20,"退出登录成功");
+    private Integer code;
+    private String msg;
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+    SuccessResultCode(Integer code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+}

+ 6 - 0
src/main/java/work/baiyun/chronicdiseaseapp/exception/CustomException.java

@@ -0,0 +1,6 @@
+package work.baiyun.chronicdiseaseapp.exception;
+
+public class CustomException extends Exception {
+    public CustomException(String message) { super(message);
+    }
+}

+ 76 - 0
src/main/java/work/baiyun/chronicdiseaseapp/exception/CustomExceptionHandler.java

@@ -0,0 +1,76 @@
+package work.baiyun.chronicdiseaseapp.exception;
+
+import jakarta.validation.*;
+import work.baiyun.chronicdiseaseapp.common.R;
+import work.baiyun.chronicdiseaseapp.enums.ExceptionResultCode;
+import org.springframework.validation.BindException;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ *
+ * @description: 异常信息统一拦截
+ */
+@RestControllerAdvice
+public class CustomExceptionHandler {
+    /**
+     * 参数校验异常
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(value = BindException.class)
+    public R errorHandler(BindException ex) {
+        BindingResult result = ex.getBindingResult();
+        StringBuilder errorMsg = new StringBuilder();
+        for (ObjectError error : result.getAllErrors()) {
+            errorMsg.append(error.getDefaultMessage()).append(", ");
+        }
+        errorMsg.delete(errorMsg.length() - 2, errorMsg.length());
+        return R.fail(ExceptionResultCode.VALID_EXCEPTION.getCode(), errorMsg.toString());
+    }
+    /**
+     * 参数校验异常
+     *
+     * @param ex
+     * @return
+     */
+    @ExceptionHandler(ConstraintViolationException.class)
+    public R validationErrorHandler(ConstraintViolationException ex) {
+        List<String> errorInformation = ex.getConstraintViolations()
+                .stream()
+                .map(ConstraintViolation::getMessage)
+                .collect(Collectors.toList());
+        String message = errorInformation.toString().substring(1, errorInformation.toString().length() - 1);
+        return R.fail(ExceptionResultCode.VALID_EXCEPTION.getCode(), message);
+    }
+    /**
+     * 自定义异常
+     *
+     * @param customException
+     * @return
+     */
+    @ExceptionHandler(value = CustomException.class)
+    public R customExceptionHandler(CustomException customException) {
+        String message = customException.getMessage();
+        return R.fail(ExceptionResultCode.EXCEPTION.getCode(), message);
+    }
+    /**
+     * 未知异常
+     *
+     * @param exception
+     * @return
+     */
+    @ExceptionHandler(value = Exception.class)
+    public R exceptionHandler(Exception exception) {
+        exception.printStackTrace();
+        return R.fail(ExceptionResultCode.EXCEPTION.getCode(), ExceptionResultCode.EXCEPTION.getMsg());
+    }
+}

+ 37 - 0
src/main/java/work/baiyun/chronicdiseaseapp/handler/CustomMetaObjectHandler.java

@@ -0,0 +1,37 @@
+package work.baiyun.chronicdiseaseapp.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+@Component
+public class CustomMetaObjectHandler implements MetaObjectHandler {
+    public static final Long currentUserInfoId=1L;
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        // 创建时间
+        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
+        // 更新时间
+        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
+        // todo 创建⼈为空,则写⼊默认值(后期需要获取系统的登陆⼈ID)
+        // String currentUserInfoId = SecurityContextHolder.getContext();
+        if (ObjectUtil.isEmpty(getFieldValByName("createUser",metaObject))) {
+            this.strictInsertFill(metaObject, "createUser", Long.class, currentUserInfoId);
+        }
+        if (ObjectUtil.isEmpty(getFieldValByName("updateUser",metaObject))) {
+            this.strictInsertFill(metaObject, "updateUser", Long.class, currentUserInfoId);
+        }
+    }
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        // 更新时间
+        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
+// String currentUserInfoId = SecurityContextHolder.getContext();
+        if (ObjectUtil.isEmpty(getFieldValByName("updateUser",metaObject))) {
+            this.strictInsertFill(metaObject, "updateUser", Long.class, currentUserInfoId);
+        }
+    }
+}

+ 41 - 0
src/main/java/work/baiyun/chronicdiseaseapp/handler/PermissionGroupTypeHandler.java

@@ -0,0 +1,41 @@
+package work.baiyun.chronicdiseaseapp.handler;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedTypes;
+import work.baiyun.chronicdiseaseapp.enums.PermissionGroup;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+@MappedTypes(PermissionGroup.class)
+public class PermissionGroupTypeHandler extends BaseTypeHandler<PermissionGroup> {
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, PermissionGroup parameter, JdbcType jdbcType) throws SQLException {
+        ps.setInt(i, parameter.getCode());
+    }
+
+    @Override
+    public PermissionGroup getNullableResult(ResultSet rs, String columnName) throws SQLException {
+        int code = rs.getInt(columnName);
+        if (rs.wasNull()) return null;
+        return PermissionGroup.fromCode(code);
+    }
+
+    @Override
+    public PermissionGroup getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
+        int code = rs.getInt(columnIndex);
+        if (rs.wasNull()) return null;
+        return PermissionGroup.fromCode(code);
+    }
+
+    @Override
+    public PermissionGroup getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
+        int code = cs.getInt(columnIndex);
+        if (cs.wasNull()) return null;
+        return PermissionGroup.fromCode(code);
+    }
+}

+ 9 - 0
src/main/java/work/baiyun/chronicdiseaseapp/mapper/SysUserMapper.java

@@ -0,0 +1,9 @@
+package work.baiyun.chronicdiseaseapp.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import work.baiyun.chronicdiseaseapp.model.po.UserToken;
+@Mapper
+public interface SysUserMapper extends BaseMapper<UserToken> {
+
+}

+ 9 - 0
src/main/java/work/baiyun/chronicdiseaseapp/mapper/UserLoginMapper.java

@@ -0,0 +1,9 @@
+package work.baiyun.chronicdiseaseapp.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import work.baiyun.chronicdiseaseapp.model.bo.UserLoginBo;
+@Mapper
+public interface UserLoginMapper extends BaseMapper<UserLoginBo> {
+
+}

+ 10 - 0
src/main/java/work/baiyun/chronicdiseaseapp/mapper/UserTokenMapper.java

@@ -0,0 +1,10 @@
+package work.baiyun.chronicdiseaseapp.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import work.baiyun.chronicdiseaseapp.model.po.UserToken;
+
+@Mapper
+public interface UserTokenMapper extends BaseMapper<UserToken> {
+
+}

+ 11 - 0
src/main/java/work/baiyun/chronicdiseaseapp/model/bo/UserLoginBo.java

@@ -0,0 +1,11 @@
+package work.baiyun.chronicdiseaseapp.model.bo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+public class UserLoginBo {
+    @Schema(description = "")
+    private String wx_openid;
+    /** 密码;密码 */
+    @Schema(description = "密码")
+    private String wx_session_key;
+}

+ 55 - 0
src/main/java/work/baiyun/chronicdiseaseapp/model/po/BaseEntity.java

@@ -0,0 +1,55 @@
+package work.baiyun.chronicdiseaseapp.model.po;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+public class BaseEntity implements Serializable {
+    private static final long serialVersionUID = -569425995057788007L;
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    @JsonSerialize(using= ToStringSerializer.class)
+    private Long id;
+    @TableField("version")
+    @Version
+    private Integer version;
+    /**
+     * 创建者
+     */
+    @TableField(value = "create_user",fill = FieldFill.INSERT)
+    @JsonSerialize(using= ToStringSerializer.class)
+    private Long createUser;
+    /**
+     * 创建时间
+     */
+    @TableField(value = "create_time",fill = FieldFill.INSERT)
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+    /**
+     * 更新者
+     */
+    @TableField(value = "update_user",fill = FieldFill.INSERT)
+    @JsonSerialize(using= ToStringSerializer.class)
+    private Long updateUser;
+    /**
+     * 更新时间
+     */
+    @TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+    /**
+     * 备注
+     */
+    @TableField(value = "remark")
+    private String remark;
+}

+ 46 - 0
src/main/java/work/baiyun/chronicdiseaseapp/model/po/UserInfo.java

@@ -0,0 +1,46 @@
+package work.baiyun.chronicdiseaseapp.model.po;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import work.baiyun.chronicdiseaseapp.enums.PermissionGroup;
+
+@Schema(description = "⽤户信息表")
+@TableName("t_user_info")
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class UserInfo extends BaseEntity {
+    /** 用户名;用户名 */
+    @Schema(description = "用户名(可选)")
+    private String username;
+    /** 密码;密码 */
+    @Schema(description = "密码(可选)")
+    private String password;
+    /** 角色;角色 */
+    @Schema(description = "角色", required = true)
+    @TableField(typeHandler = work.baiyun.chronicdiseaseapp.handler.PermissionGroupTypeHandler.class)
+    private PermissionGroup role;
+    /** wx_openid;wx_openid */
+    @Schema(description = "微信ID(可选)")
+    private String wx_openid;
+    /** 头像;头像 */
+    @Schema(description = "头像(可选)")
+    private String avatar;
+    /** 昵称;昵称 */
+    @Schema(description = "昵称(可选)")
+    private String nickname;
+    /** 性别;性别 */
+    @Schema(description = "性别(可选)")
+    private String sex;
+    /** 手机号;手机号 */
+    @Schema(description = "手机号(可选)")
+    private String phone;
+    /** 年龄;年龄 */
+    @Schema(description = "年龄(可选)")
+    private Integer age;
+    /** 地址;地址 */
+    @Schema(description = "地址(可选)")
+    private String address;
+}

+ 32 - 0
src/main/java/work/baiyun/chronicdiseaseapp/model/po/UserToken.java

@@ -0,0 +1,32 @@
+package work.baiyun.chronicdiseaseapp.model.po;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+/**
+ * ⽤户认证表
+ * 当前设计:
+ * 1. 每次登录成功后,生成新的token,覆盖旧的token
+ * 2. token有效期为72小时
+ * 3. 每次请求时,检查token是否过期,若过期则返回未登录状态
+ * 4. 每次请求时,若token快过期(小于24小时),则延长token有效期
+ * 5. token的生成格式为uuid,存储在DB中
+ */
+
+@Schema(description = "⽤户认证表")
+@TableName("t_user_token")
+@Data
+public class UserToken extends BaseEntity{
+    /** 用户ID;用户ID */
+    @Schema(description = "用户ID;用户ID")
+    private Long user_id;
+    /** Token;Token */
+    @Schema(description = "Token")
+    private String token;
+    /** 过期时间;过期时间 */
+    @Schema(description = "过期时间")
+    private LocalDateTime expire_time;
+
+}

+ 8 - 0
src/main/java/work/baiyun/chronicdiseaseapp/model/vo/GetOpenidRequest.java

@@ -0,0 +1,8 @@
+package work.baiyun.chronicdiseaseapp.model.vo;
+
+import lombok.Data;
+
+@Data
+public class GetOpenidRequest {
+    private String code;
+}

+ 19 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/TokenService.java

@@ -0,0 +1,19 @@
+package work.baiyun.chronicdiseaseapp.service;
+
+public interface TokenService {
+    /**
+     * 为用户生成新的 token,并保存(覆盖旧的 token)。返回生成的 token 字符串。
+     */
+    String createToken(Long userId);
+
+    /**
+     * 根据 token 校验并返回 userId;若无效或过期返回 null。
+     * 在校验过程中会处理临近过期(小于24小时)时延长有效期的逻辑。
+     */
+    Long validateToken(String token);
+
+    /**
+     * 撤销 token(例如登出)
+     */
+    void revokeToken(String token);
+}

+ 16 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/UserLoginService.java

@@ -0,0 +1,16 @@
+package work.baiyun.chronicdiseaseapp.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import work.baiyun.chronicdiseaseapp.exception.CustomException;
+import work.baiyun.chronicdiseaseapp.model.bo.UserLoginBo;
+
+public interface UserLoginService extends IService<UserLoginBo> {
+    /**
+     * 用户登录
+     *
+     * @param userLoginBo
+     * @return
+     * @throws CustomException
+     */
+    Boolean login(UserLoginBo userLoginBo) throws CustomException;
+}

+ 9 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/UserService.java

@@ -0,0 +1,9 @@
+package work.baiyun.chronicdiseaseapp.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import work.baiyun.chronicdiseaseapp.model.po.UserToken;
+
+public interface UserService extends IService<UserToken> {
+
+}

+ 10 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/WeChatService.java

@@ -0,0 +1,10 @@
+package work.baiyun.chronicdiseaseapp.service;
+
+public interface WeChatService {
+    /**
+     * 根据前端传来的 code 调用微信 jscode2session 接口并返回 openid
+     * @param code 小程序登录时获取的 code
+     * @return openid 字符串,如果失败则返回 null
+     */
+    String getOpenId(String code);
+}

+ 79 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/impl/TokenServiceImpl.java

@@ -0,0 +1,79 @@
+package work.baiyun.chronicdiseaseapp.service.impl;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import work.baiyun.chronicdiseaseapp.mapper.UserTokenMapper;
+import work.baiyun.chronicdiseaseapp.model.po.UserToken;
+import work.baiyun.chronicdiseaseapp.service.TokenService;
+import work.baiyun.chronicdiseaseapp.util.TokenUtil;
+
+import java.time.LocalDateTime;
+
+@Service
+public class TokenServiceImpl implements TokenService {
+
+    private final UserTokenMapper userTokenMapper;
+
+    @Autowired
+    public TokenServiceImpl(UserTokenMapper userTokenMapper) {
+        this.userTokenMapper = userTokenMapper;
+    }
+
+    @Override
+    @Transactional
+    public String createToken(Long userId) {
+        String token = TokenUtil.generateToken();
+        LocalDateTime expire = LocalDateTime.now().plusHours(TokenUtil.TOKEN_TTL_HOURS);
+
+        // 查询是否已有记录
+        UserToken existing = userTokenMapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<UserToken>().eq("user_id", userId));
+        if (existing == null) {
+            UserToken ut = new UserToken();
+            ut.setUser_id(userId);
+            ut.setToken(token);
+            ut.setExpire_time(expire);
+            userTokenMapper.insert(ut);
+        } else {
+            existing.setToken(token);
+            existing.setExpire_time(expire);
+            userTokenMapper.updateById(existing);
+        }
+        return token;
+    }
+
+    @Override
+    public Long validateToken(String token) {
+        if (token == null || token.isEmpty()) return null;
+        UserToken ut = userTokenMapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<UserToken>().eq("token", token));
+        if (ut == null) return null;
+
+        LocalDateTime now = LocalDateTime.now();
+        if (ut.getExpire_time() == null || ut.getExpire_time().isBefore(now)) {
+            // expired
+            return null;
+        }
+
+        // if token will expire in less than REFRESH_THRESHOLD_HOURS, extend it
+        LocalDateTime refreshThreshold = now.plusHours(TokenUtil.REFRESH_THRESHOLD_HOURS);
+        if (ut.getExpire_time().isBefore(refreshThreshold)) {
+            ut.setExpire_time(now.plusHours(TokenUtil.TOKEN_TTL_HOURS));
+            userTokenMapper.updateById(ut);
+        }
+
+        return ut.getUser_id();
+    }
+
+    @Override
+    @Transactional
+    public void revokeToken(String token) {
+        if (token == null || token.isEmpty()) return;
+        UserToken ut = userTokenMapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<UserToken>().eq("token", token));
+        if (ut != null) {
+            // 删除或清空 token 字段以表示失效
+            ut.setToken(null);
+            ut.setExpire_time(null);
+            userTokenMapper.updateById(ut);
+        }
+    }
+}

+ 17 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/impl/UserLoginServiceImpl.java

@@ -0,0 +1,17 @@
+package work.baiyun.chronicdiseaseapp.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import work.baiyun.chronicdiseaseapp.exception.CustomException;
+import work.baiyun.chronicdiseaseapp.mapper.UserLoginMapper;
+import work.baiyun.chronicdiseaseapp.model.bo.UserLoginBo;
+import work.baiyun.chronicdiseaseapp.service.UserLoginService;
+
+@Service
+public class UserLoginServiceImpl extends ServiceImpl<UserLoginMapper, UserLoginBo> implements UserLoginService {
+
+    @Override
+    public Boolean login(UserLoginBo userLoginBo) throws CustomException {
+        return true;
+    }
+}

+ 12 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/impl/UserServiceImpl.java

@@ -0,0 +1,12 @@
+package work.baiyun.chronicdiseaseapp.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import work.baiyun.chronicdiseaseapp.mapper.SysUserMapper;
+import work.baiyun.chronicdiseaseapp.model.po.UserToken;
+import work.baiyun.chronicdiseaseapp.service.UserService;
+
+@Service
+public class UserServiceImpl extends ServiceImpl<SysUserMapper, UserToken> implements UserService {
+
+}

+ 51 - 0
src/main/java/work/baiyun/chronicdiseaseapp/service/impl/WeChatServiceImpl.java

@@ -0,0 +1,51 @@
+package work.baiyun.chronicdiseaseapp.service.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+import work.baiyun.chronicdiseaseapp.config.WeChatProperties;
+import work.baiyun.chronicdiseaseapp.service.WeChatService;
+
+@Service
+public class WeChatServiceImpl implements WeChatService {
+
+    private static final Logger logger = LoggerFactory.getLogger(WeChatServiceImpl.class);
+
+    @Autowired
+    private WeChatProperties weChatProperties;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    public String getOpenId(String code) {
+        if (code == null || code.isEmpty()) {
+            logger.debug("getOpenId called with empty code");
+            return null;
+        }
+        String url = String.format(
+                "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
+                weChatProperties.getAppid(), weChatProperties.getSecret(), code
+        );
+        try {
+            logger.debug("Requesting Weixin API URL: {}", url);
+            String resp = restTemplate.getForObject(url, String.class);
+            logger.debug("Weixin API response: {}", resp);
+            JsonNode node = objectMapper.readTree(resp);
+            if (node.has("openid")) {
+                String openid = node.get("openid").asText();
+                logger.debug("Got openid: {}", openid);
+                return openid;
+            }
+            logger.warn("Weixin API did not return openid. Response: {}", resp);
+            return null;
+        } catch (Exception e) {
+            logger.error("Error while calling Weixin API", e);
+            return null;
+        }
+    }
+}

+ 14 - 0
src/main/java/work/baiyun/chronicdiseaseapp/util/TokenUtil.java

@@ -0,0 +1,14 @@
+package work.baiyun.chronicdiseaseapp.util;
+
+import java.util.UUID;
+
+public class TokenUtil {
+    // Token 有效期:72 小时
+    public static final long TOKEN_TTL_HOURS = 72L;
+    // 小于 24 小时则刷新
+    public static final long REFRESH_THRESHOLD_HOURS = 24L;
+
+    public static String generateToken() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+}

+ 49 - 0
src/main/resources/application.yml

@@ -0,0 +1,49 @@
+server:
+  port: 8080
+# 配置⽇志输出级别
+logging:
+  level:
+    root: info
+    work.baiyun.chronicdiseaseapp: debug
+spring:
+  main:
+    allow-circular-references: true
+    allow-bean-definition-overriding: true
+  application:
+    name: chronic-disease-app
+  # 配置数据库连接
+  datasource:
+    url: jdbc:mysql://127.0.0.1/chronicdiseaseapp?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf-8&characterEncoding=utf-8
+    username: root
+    password: root
+    driver-class-name: com.mysql.jdbc.Driver
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 5
+      maximum-pool-size: 20
+      idle-timeout: 60000
+      max-lifetime: 60000
+      connection-timeout: 30000
+      pool-name: HikariDataSource
+# mybatis-plus 配置
+mybatis-plus:
+  configuration:
+    map-underscore-to-camel-case: true
+    # 打印sql语句
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+# 分⻚插件配置
+pagehelper:
+  reasonable: true # 禁⽤合理化时
+  support-methods-arguments: true
+  params: count=countSql
+  row-bounds-with-count: true
+  helper-dialect: mysql
+# knife4j的增强配置,不需要增强可以不配
+knife4j:
+  enable: true
+  setting:
+    language: zh_cn
+# 微信小程序配置
+wechat:
+  appid: wx334b14b3d0bb1547 # TODO: replace with your AppID
+  secret: 0b42a3e4becb7817d08e44e91e2824dd # TODO: replace with your AppSecret

+ 49 - 0
target/classes/application.yml

@@ -0,0 +1,49 @@
+server:
+  port: 8080
+# 配置⽇志输出级别
+logging:
+  level:
+    root: info
+    work.baiyun.chronicdiseaseapp: debug
+spring:
+  main:
+    allow-circular-references: true
+    allow-bean-definition-overriding: true
+  application:
+    name: chronic-disease-app
+  # 配置数据库连接
+  datasource:
+    url: jdbc:mysql://127.0.0.1/chronicdiseaseapp?serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf-8&characterEncoding=utf-8
+    username: root
+    password: root
+    driver-class-name: com.mysql.jdbc.Driver
+    type: com.zaxxer.hikari.HikariDataSource
+    hikari:
+      minimum-idle: 5
+      maximum-pool-size: 20
+      idle-timeout: 60000
+      max-lifetime: 60000
+      connection-timeout: 30000
+      pool-name: HikariDataSource
+# mybatis-plus 配置
+mybatis-plus:
+  configuration:
+    map-underscore-to-camel-case: true
+    # 打印sql语句
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+# 分⻚插件配置
+pagehelper:
+  reasonable: true # 禁⽤合理化时
+  support-methods-arguments: true
+  params: count=countSql
+  row-bounds-with-count: true
+  helper-dialect: mysql
+# knife4j的增强配置,不需要增强可以不配
+knife4j:
+  enable: true
+  setting:
+    language: zh_cn
+# 微信小程序配置
+wechat:
+  appid: wx334b14b3d0bb1547 # TODO: replace with your AppID
+  secret: 0b42a3e4becb7817d08e44e91e2824dd # TODO: replace with your AppSecret