Prechádzať zdrojové kódy

# feat:系统元数据api开发完成

yangyi 6 dní pred
rodič
commit
bef5d28a84

+ 28 - 0
sql/postgersql.sql

@@ -50,3 +50,31 @@ CREATE INDEX idx_user_account ON dev.user(account);
 CREATE INDEX idx_user_role ON dev.user(role);
 CREATE INDEX idx_user_enable ON dev.user(enable);
 CREATE INDEX idx_user_delete_flag ON dev.user(delete_flag);
+
+-- 元数据
+CREATE TABLE dev.meta (
+                      id    SERIAL PRIMARY KEY,
+                      key   VARCHAR(32) NOT NULL,
+                      value JSONB
+);
+
+-- 2. 添加注释
+COMMENT ON TABLE dev.meta IS '元数据表,用于存储键值对形式的配置或扩展属性';
+COMMENT ON COLUMN dev.meta.id IS '自增主键,唯一标识一条元数据记录';
+COMMENT ON COLUMN dev.meta.key IS '元数据的键,长度不超过32个字符,不可为空';
+COMMENT ON COLUMN dev.meta.value IS '元数据的值,使用 JSONB 类型存储,支持结构化数据';
+
+-- 3. 索引建议
+-- 业务上 key 通常需要唯一约束,且频繁用于等值查询
+CREATE UNIQUE INDEX idx_meta_key ON dev.meta (key);
+
+-- 若经常对 value 内部字段进行查询或过滤,可添加 GIN 索引以加速 JSONB 操作
+CREATE INDEX idx_meta_value_gin ON dev.meta USING GIN (value);
+
+INSERT INTO dev.meta(key, value) VALUES
+                                     ('website_config','{
+                                       "title": "示例网站",
+                                       "logo": "",
+                                       "announcement": "欢迎访问我们的网站!",
+                                       "statement": "版权所有 © 2025 示例网站"
+                                     }');

+ 2 - 0
src/main/java/space/anyi/serve/config/SecurityConfig.java

@@ -53,6 +53,8 @@ public class SecurityConfig {
                 .authorizeHttpRequests(authorize -> authorize
                         .requestMatchers("/auth/login", "/auth/register").permitAll()
                         .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
+                        .requestMatchers(HttpMethod.GET,"/meta/**").permitAll()
+                        .requestMatchers(HttpMethod.POST,"/meta/**").permitAll()
                         .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                         .anyRequest().authenticated()
                 )

+ 51 - 0
src/main/java/space/anyi/serve/controller/MetaController.java

@@ -0,0 +1,51 @@
+package space.anyi.serve.controller;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.validation.Valid;
+import org.springframework.web.bind.annotation.GetMapping;
+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 space.anyi.serve.entity.Response;
+import space.anyi.serve.entity.meta.Meta;
+import space.anyi.serve.entity.meta.WebsiteMetaDto;
+import space.anyi.serve.service.MetaService;
+
+/**
+ * @fileName: MetaController
+ * @projectName: serve
+ * @package: space.anyi.serve.controller
+ * @author: yangyi
+ * @date:14/6/2026 2:38 pm
+ * @description:
+ */
+@RestController
+@RequestMapping("meta")
+public class MetaController {
+    public final MetaService metaService;
+    public final ObjectMapper objectMapper;
+
+    public MetaController(MetaService metaService, ObjectMapper objectMapper) {
+        this.metaService = metaService;
+        this.objectMapper = objectMapper;
+    }
+
+
+    @GetMapping("getWebsiteMeta")
+    public Response getWebsiteMeta() throws JsonProcessingException {
+        Meta meta = metaService.getMeta(Meta.WEBSITE_META_KEY);
+        return Response.ok(meta.getValue());
+    }
+
+    @PostMapping("updateWebsiteMeta")
+    public Response updateWebsiteMeta(@Valid @RequestBody WebsiteMetaDto websiteMetaDto){
+        Meta meta = new Meta();
+        meta.setKey(Meta.WEBSITE_META_KEY);
+        meta.setValue(websiteMetaDto);
+        metaService.updateMeta(Meta.WEBSITE_META_KEY,meta);
+        return Response.ok();
+    }
+
+}

+ 119 - 0
src/main/java/space/anyi/serve/entity/meta/Meta.java

@@ -0,0 +1,119 @@
+package space.anyi.serve.entity.meta;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+/**
+ * 元数据表,用于存储键值对形式的配置或扩展属性
+ * @TableName meta
+ */
+@TableName(value ="dev.meta")
+@Schema(description = "元数据实体,存储键值对形式的配置")
+public class Meta {
+    public static final String WEBSITE_META_KEY = "website_config";
+    /**
+     * 自增主键,唯一标识一条元数据记录
+     */
+    @TableId
+    @Schema(description = "自增主键")
+    private Integer id;
+
+    /**
+     * 元数据的键,长度不超过32个字符,不可为空
+     */
+    @NotBlank(message = "元数据键不能为空")
+    @Size(max = 32, message = "元数据键长度不能超过32个字符")
+    @Schema(description = "元数据的键", maxLength = 32)
+    private String key;
+
+    /**
+     * 元数据的值,使用 JSONB 类型存储,支持结构化数据
+     */
+    @Schema(description = "元数据的值,JSONB 格式")
+    private Object value;
+
+    /**
+     * 自增主键,唯一标识一条元数据记录
+     */
+    public Integer getId() {
+        return id;
+    }
+
+    /**
+     * 自增主键,唯一标识一条元数据记录
+     */
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    /**
+     * 元数据的键,长度不超过32个字符,不可为空
+     */
+    public String getKey() {
+        return key;
+    }
+
+    /**
+     * 元数据的键,长度不超过32个字符,不可为空
+     */
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    /**
+     * 元数据的值,使用 JSONB 类型存储,支持结构化数据
+     */
+    public Object getValue() {
+        return value;
+    }
+
+    /**
+     * 元数据的值,使用 JSONB 类型存储,支持结构化数据
+     */
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        Meta other = (Meta) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getKey() == null ? other.getKey() == null : this.getKey().equals(other.getKey()))
+            && (this.getValue() == null ? other.getValue() == null : this.getValue().equals(other.getValue()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getKey() == null) ? 0 : getKey().hashCode());
+        result = prime * result + ((getValue() == null) ? 0 : getValue().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", key=").append(key);
+        sb.append(", value=").append(value);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 63 - 0
src/main/java/space/anyi/serve/entity/meta/WebsiteMetaDto.java

@@ -0,0 +1,63 @@
+package space.anyi.serve.entity.meta;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Size;
+
+/**
+ * @fileName: WebsiteMetaDto
+ * @projectName: serve
+ * @package: space.anyi.serve.entity.meta
+ * @author: yangyi
+ * @date:14/6/2026 6:46 pm
+ * @description:
+ */
+@Schema(description = "网站元数据DTO,用于更新网站配置信息")
+public class WebsiteMetaDto {
+    @Size(max = 200, message = "网站标题长度不能超过200个字符")
+    @Schema(description = "网站标题", maxLength = 200)
+    String title = "";
+
+    @Size(max = 500, message = "网站logo URL长度不能超过500个字符")
+    @Schema(description = "网站logo URL")
+    String logo = "";
+
+    @Size(max = 500, message = "网站声明长度不能超过500个字符")
+    @Schema(description = "网站声明", maxLength = 500)
+    String statement = "";
+
+    @Size(max = 500, message = "网站公告长度不能超过500个字符")
+    @Schema(description = "网站公告", maxLength = 500)
+    String announcement = "";
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getLogo() {
+        return logo;
+    }
+
+    public void setLogo(String logo) {
+        this.logo = logo;
+    }
+
+    public String getStatement() {
+        return statement;
+    }
+
+    public void setStatement(String statement) {
+        this.statement = statement;
+    }
+
+    public String getAnnouncement() {
+        return announcement;
+    }
+
+    public void setAnnouncement(String announcement) {
+        this.announcement = announcement;
+    }
+}

+ 21 - 0
src/main/java/space/anyi/serve/mapper/MetaMapper.java

@@ -0,0 +1,21 @@
+package space.anyi.serve.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import space.anyi.serve.entity.meta.Meta;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author yangyi
+* @description 针对表【meta(元数据表,用于存储键值对形式的配置或扩展属性)】的数据库操作Mapper
+* @createDate 2026-06-14 14:33:39
+* @Entity space.anyi.serve.entity.meta.Meta
+*/
+@Mapper
+public interface MetaMapper extends BaseMapper<Meta> {
+
+    Boolean updateValueByKey(String key, String value);
+}
+
+
+
+

+ 17 - 0
src/main/java/space/anyi/serve/service/MetaService.java

@@ -0,0 +1,17 @@
+package space.anyi.serve.service;
+
+import space.anyi.serve.entity.meta.Meta;
+import com.baomidou.mybatisplus.extension.service.IService;
+import space.anyi.serve.entity.meta.WebsiteMetaDto;
+
+/**
+* @author yangyi
+* @description 针对表【meta(元数据表,用于存储键值对形式的配置或扩展属性)】的数据库操作Service
+* @createDate 2026-06-14 14:33:39
+*/
+public interface MetaService extends IService<Meta> {
+
+    Meta getMeta(String key);
+
+    Boolean updateMeta(String websiteMetaKey, Meta meta);
+}

+ 55 - 0
src/main/java/space/anyi/serve/service/impl/MetaServiceImpl.java

@@ -0,0 +1,55 @@
+package space.anyi.serve.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import space.anyi.serve.entity.meta.Meta;
+import space.anyi.serve.entity.meta.WebsiteMetaDto;
+import space.anyi.serve.service.MetaService;
+import space.anyi.serve.mapper.MetaMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author yangyi
+* @description 针对表【meta(元数据表,用于存储键值对形式的配置或扩展属性)】的数据库操作Service实现
+* @createDate 2026-06-14 14:33:39
+*/
+@Service
+public class MetaServiceImpl extends ServiceImpl<MetaMapper, Meta>
+    implements MetaService{
+    public static final Logger log = LoggerFactory.getLogger(MetaServiceImpl.class);
+    public final ObjectMapper objectMapper;
+
+    public MetaServiceImpl(ObjectMapper objectMapper) {
+        this.objectMapper = objectMapper;
+    }
+
+    @Override
+    public Boolean updateMeta(String key, Meta meta) {
+        try {
+            //UpdateWrapper<Meta> updateWrapper = new UpdateWrapper<Meta>().eq("key", key).set("value", objectMapper.writeValueAsString(meta.getValue()));
+            //return update(updateWrapper);
+            return getBaseMapper().updateValueByKey(key, objectMapper.writeValueAsString(meta.getValue()));
+        } catch (JsonProcessingException e) {
+            log.error("网站元素对象序列化为JSON失败",e);
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public Meta getMeta(String key) {
+        LambdaQueryWrapper<Meta> queryWrapper = new LambdaQueryWrapper<Meta>()
+                .eq(Meta::getKey, key);
+        return getOne(queryWrapper);
+    }
+}
+
+
+
+

+ 21 - 0
src/main/resources/mapper/MetaMapper.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="space.anyi.serve.mapper.MetaMapper">
+
+    <resultMap id="BaseResultMap" type="space.anyi.serve.entity.meta.Meta">
+            <id property="id" column="id" />
+            <result property="key" column="key" />
+            <result property="value" column="value" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,key,value
+    </sql>
+    <update id="updateValueByKey">
+        UPDATE meta
+        SET value = value || #{value}::jsonb
+        WHERE key = #{key};
+    </update>
+</mapper>