diff --git a/docs/sql/mysql/t_platform_user.sql b/docs/sql/mysql/t_platform_user.sql index f6c92fe..c897ff7 100644 --- a/docs/sql/mysql/t_platform_user.sql +++ b/docs/sql/mysql/t_platform_user.sql @@ -9,7 +9,7 @@ CREATE TABLE t_platform_user ( last_login_time TIMESTAMP NULL COMMENT '最后登录时间', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - deleted TINYINT DEFAULT 0 COMMENT '软删除:0-未删,1-已删', + delFlag TINYINT DEFAULT 0 COMMENT '软删除:0-未删,1-已删', UNIQUE KEY uk_platform_openid (platform_type, platform_openid), CONSTRAINT fk_platform_user_user_id FOREIGN KEY (user_id) REFERENCES t_user(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台用户关联表(微信/抖音小程序用户信息)'; diff --git a/docs/sql/mysql/t_user.sql b/docs/sql/mysql/t_user.sql index cde5085..79191e0 100644 --- a/docs/sql/mysql/t_user.sql +++ b/docs/sql/mysql/t_user.sql @@ -10,5 +10,5 @@ CREATE TABLE t_user ( status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-正常', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - deleted TINYINT DEFAULT 0 COMMENT '软删除:0-未删,1-已删' + delFlag TINYINT DEFAULT 0 COMMENT '软删除:0-未删,1-已删' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户基础信息表'; diff --git a/docs/sql/postgresql/t_platform_user.sql b/docs/sql/postgresql/t_platform_user.sql index a0d26f3..ccf14d3 100644 --- a/docs/sql/postgresql/t_platform_user.sql +++ b/docs/sql/postgresql/t_platform_user.sql @@ -9,7 +9,7 @@ CREATE TABLE t_platform_user ( last_login_time TIMESTAMP, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - deleted SMALLINT DEFAULT 0, + delFlag SMALLINT DEFAULT 0, UNIQUE (platform_type, platform_openid), CONSTRAINT fk_platform_user_user_id FOREIGN KEY (user_id) REFERENCES t_user(id) ON DELETE CASCADE ); @@ -25,7 +25,7 @@ COMMENT ON COLUMN t_platform_user.platform_extra IS '平台扩展字段(如抖 COMMENT ON COLUMN t_platform_user.last_login_time IS '最后登录时间'; COMMENT ON COLUMN t_platform_user.create_time IS '创建时间'; COMMENT ON COLUMN t_platform_user.update_time IS '更新时间'; -COMMENT ON COLUMN t_platform_user.deleted IS '软删除:0-未删,1-已删'; +COMMENT ON COLUMN t_platform_user.delFlag IS '软删除:0-未删,1-已删'; CREATE OR REPLACE FUNCTION set_update_time() RETURNS TRIGGER AS $$ diff --git a/docs/sql/postgresql/t_user.sql b/docs/sql/postgresql/t_user.sql index 789277c..e430593 100644 --- a/docs/sql/postgresql/t_user.sql +++ b/docs/sql/postgresql/t_user.sql @@ -10,7 +10,7 @@ CREATE TABLE t_user ( status SMALLINT DEFAULT 1, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - deleted SMALLINT DEFAULT 0, + delFlag SMALLINT DEFAULT 0, UNIQUE (phone) ); @@ -26,7 +26,7 @@ COMMENT ON COLUMN t_user.gender IS '性别:0-未知,1-男,2-女'; COMMENT ON COLUMN t_user.status IS '状态:0-禁用,1-正常'; COMMENT ON COLUMN t_user.create_time IS '创建时间'; COMMENT ON COLUMN t_user.update_time IS '更新时间'; -COMMENT ON COLUMN t_user.deleted IS '软删除:0-未删,1-已删'; +COMMENT ON COLUMN t_user.delFlag IS '软删除:0-未删,1-已删'; CREATE OR REPLACE FUNCTION set_update_time() RETURNS TRIGGER AS $$ diff --git a/frontend_payload_crypto_tasks.md b/frontend_payload_crypto_tasks.md new file mode 100644 index 0000000..a5b03fe --- /dev/null +++ b/frontend_payload_crypto_tasks.md @@ -0,0 +1,70 @@ +# 前端工作清单:请求/响应参数加密对接 + +本文档用于前端工程师对接后端新增的“请求/响应参数加密”能力。 + +## 一、背景与目标 + +- 后端新增可配置的参数加密能力(默认关闭)。 +- 当服务端开启时,前端需按约定对请求体进行加密,并能解密响应体。 + +## 二、开关与触发规则 + +- 只有当后端配置开启时才需要启用前端加密。 +- 通过请求头 `X-App-Encrypt: 1` 标记本次请求体已加密。 +- 如果后端开启“响应加密”,返回会带响应头 `X-App-Encrypt: 1`,响应体为加密载荷。 +- 白名单路径(如 `/swagger/`)不会被加解密;前端无需处理。 + +## 三、加密协议与格式 + +- 加密算法:AES-GCM。 +- 密钥:后端配置 `payload_crypto.secret_key`,需与前端一致。 +- 请求/响应体加密后的 JSON 结构: + +```json +{ "nonce": "base64", "ciphertext": "base64" } +``` + +- `nonce` 为 AES-GCM 的随机随机数(Base64),`ciphertext` 为密文(Base64)。 + +## 四、前端需要实现的功能 + +1. **请求加密** + - 当后端开启 `payload_crypto.request.enable` 时,对请求 JSON 体加密。 + - 请求头添加 `X-App-Encrypt: 1`。 + - 发送的请求体替换为加密后的 JSON 结构(`nonce` + `ciphertext`)。 + +2. **响应解密** + - 如果响应头包含 `X-App-Encrypt: 1`,则对响应体解密。 + - 解密后得到原始 JSON,再进入业务处理流程。 + +3. **错误处理** + - 解密失败时要能捕获并上报(日志/埋点),避免页面卡死。 + - 解密失败可提示“数据解析失败,请重试”。 + +4. **降级与兼容** + - 若响应体不是加密结构(未带头或非 JSON),直接走原逻辑。 + - 与后端保持兼容:后端若未开启加密,不应影响现有请求。 + +## 五、实现建议 + +- 统一封装在请求拦截器/响应拦截器层: + - Web:Axios 拦截器。 + - 小程序:封装 `wx.request` 统一处理。 +- AES-GCM 需支持: + - Web: Web Crypto API(`window.crypto.subtle`)。 + - 小程序: 引入可用的 AES-GCM 实现库(需确认平台支持)。 +- `nonce` 长度以 AES-GCM 要求为准(通常 12 字节)。 + +## 六、需要后端确认的信息 + +- `payload_crypto.secret_key` 的实际值(生产环境)。 +- 是否强制 `payload_crypto.request.required`(如果强制,所有接口必须加密)。 +- 哪些接口在白名单(默认 `/swagger/`)。 + +## 七、联调检查清单 + +- 请求体加密后,后端可以正常解析并返回业务数据。 +- 响应解密后,数据结构与未加密时一致。 +- 错误场景: + - 缺少 `X-App-Encrypt` 头且后端强制加密时应收到 400。 + - 非 JSON 响应不应被尝试解密。 diff --git a/server/common/payload_crypto.go b/server/common/payload_crypto.go new file mode 100644 index 0000000..7fa7066 --- /dev/null +++ b/server/common/payload_crypto.go @@ -0,0 +1,106 @@ +// Package common 参数加解密工具 +package common + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "errors" + "io" + "strings" +) + +// EncryptedPayload 加密载荷结构 +type EncryptedPayload struct { + Nonce string `json:"nonce"` + Ciphertext string `json:"ciphertext"` +} + +// EncryptPayload 加密响应内容 (AES-GCM + Base64) +func EncryptPayload(plaintext []byte, secret string) (EncryptedPayload, error) { + key, err := deriveAESKey(secret) + if err != nil { + return EncryptedPayload{}, err + } + + block, err := aes.NewCipher(key) + if err != nil { + return EncryptedPayload{}, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return EncryptedPayload{}, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return EncryptedPayload{}, err + } + + ciphertext := gcm.Seal(nil, nonce, plaintext, nil) + return EncryptedPayload{ + Nonce: base64.StdEncoding.EncodeToString(nonce), + Ciphertext: base64.StdEncoding.EncodeToString(ciphertext), + }, nil +} + +// DecryptPayload 解密请求内容 (AES-GCM + Base64) +func DecryptPayload(payload EncryptedPayload, secret string) ([]byte, error) { + key, err := deriveAESKey(secret) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce, err := base64.StdEncoding.DecodeString(payload.Nonce) + if err != nil { + return nil, err + } + if len(nonce) != gcm.NonceSize() { + return nil, errors.New("invalid nonce size") + } + + ciphertext, err := base64.StdEncoding.DecodeString(payload.Ciphertext) + if err != nil { + return nil, err + } + + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil +} + +func deriveAESKey(secret string) ([]byte, error) { + secret = strings.TrimSpace(secret) + if secret == "" { + return nil, errors.New("secret is empty") + } + + if decoded, err := base64.StdEncoding.DecodeString(secret); err == nil { + if len(decoded) == 16 || len(decoded) == 24 || len(decoded) == 32 { + return decoded, nil + } + } + + if len(secret) == 16 || len(secret) == 24 || len(secret) == 32 { + return []byte(secret), nil + } + + sum := sha256.Sum256([]byte(secret)) + return sum[:], nil +} diff --git a/server/config/config.dev.yaml b/server/config/config.dev.yaml index f6841ca..c1c5cc2 100644 --- a/server/config/config.dev.yaml +++ b/server/config/config.dev.yaml @@ -13,6 +13,19 @@ security: header_key: X-App-Sign secret_key: yts@2025#secure +payload_crypto: + enable: true + header_key: X-App-Encrypt + secret_key: "1" + whitelist: + - /swagger/ + request: + enable: false + required: false + response: + enable: false + required: false + rate_limit: enable: true default: @@ -42,9 +55,9 @@ database: host: 10.13.13.1 #port: 3306 port: 5432 - database: fast-common-db - username: user_3W72AM - password: "password_KAwdZW" + database: wz-db + username: wz-db + password: "sYpphaZWYpEEtrS7" charset: utf8mb4 max_idle_conns: 20 max_open_conns: 100 @@ -66,6 +79,7 @@ wechat: app_secret: "ed3fd9089dcfbd1d886eddeca69c07bd" app_config: + tenantId: 000000 app: min_version: "1.2.0" latest_version: "1.3.5" diff --git a/server/config/config.go b/server/config/config.go index a0dd86e..8ca1675 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -12,15 +12,16 @@ import ( var AppConfig = &appConfig{} type appConfig struct { - Log LogConfig `yaml:"log"` - Server ServerConfig `yaml:"server"` - Security SecurityConfig `yaml:"security"` - RateLimit RateLimitConfig `yaml:"rate_limit"` - Swagger SwaggerConfig `yaml:"swagger"` - Database DatabaseConfig `yaml:"database"` - Redis RedisConfig `yaml:"redis"` - Wechat WechatConfig `yaml:"wechat"` - AppConfig AppVersionConfig `yaml:"app_config"` + Log LogConfig `yaml:"log"` + Server ServerConfig `yaml:"server"` + Security SecurityConfig `yaml:"security"` + PayloadCrypto PayloadCryptoConfig `yaml:"payload_crypto"` + RateLimit RateLimitConfig `yaml:"rate_limit"` + Swagger SwaggerConfig `yaml:"swagger"` + Database DatabaseConfig `yaml:"database"` + Redis RedisConfig `yaml:"redis"` + Wechat WechatConfig `yaml:"wechat"` + AppConfig AppVersionConfig `yaml:"app_config"` } // LogConfig 日志配置 @@ -44,6 +45,22 @@ type SecurityConfig struct { SecretKey string `yaml:"secret_key"` // 签名密钥 } +// PayloadCryptoConfig 请求/响应参数加密配置 +type PayloadCryptoConfig struct { + Enable bool `yaml:"enable"` // 是否启用 + HeaderKey string `yaml:"header_key"` // 加密标记请求头字段名 + SecretKey string `yaml:"secret_key"` // 加密密钥 + Whitelist []string `yaml:"whitelist"` // 白名单路径 + Request PayloadCryptoDirectionConfig `yaml:"request"` // 请求加密配置 + Response PayloadCryptoDirectionConfig `yaml:"response"` // 响应加密配置 +} + +// PayloadCryptoDirectionConfig 方向配置 +type PayloadCryptoDirectionConfig struct { + Enable bool `yaml:"enable"` // 是否启用 + Required bool `yaml:"required"` // 是否强制 +} + // RateLimitConfig 限流配置 type RateLimitConfig struct { Enable bool `yaml:"enable"` // 是否启用 @@ -104,6 +121,7 @@ type AppVersionConfig struct { TTLSeconds int `yaml:"ttl_seconds"` Disabled bool `yaml:"disabled"` DisableReason string `yaml:"disable_reason"` + TenantId string `yaml:"tenantId"` } // AppClientConfig 客户端版本配置 diff --git a/server/config/config.prod.yaml b/server/config/config.prod.yaml index b504224..dfa9a4b 100644 --- a/server/config/config.prod.yaml +++ b/server/config/config.prod.yaml @@ -13,6 +13,19 @@ security: header_key: X-App-Sign secret_key: yts@2025#secure +payload_crypto: + enable: false + header_key: X-App-Encrypt + secret_key: "" + whitelist: + - /swagger/ + request: + enable: false + required: false + response: + enable: false + required: false + rate_limit: enable: true default: diff --git a/server/config/config.test.yaml b/server/config/config.test.yaml index 6247e30..1b28efc 100644 --- a/server/config/config.test.yaml +++ b/server/config/config.test.yaml @@ -13,6 +13,19 @@ security: header_key: X-App-Sign secret_key: yts@2025#secure +payload_crypto: + enable: false + header_key: X-App-Encrypt + secret_key: "" + whitelist: + - /swagger/ + request: + enable: false + required: false + response: + enable: false + required: false + rate_limit: enable: true default: diff --git a/server/main.go b/server/main.go index 14df254..fc3ca92 100644 --- a/server/main.go +++ b/server/main.go @@ -85,7 +85,8 @@ func main() { // API 路由组 api := r.Group("/api") - // 中间件顺序: 安全校验 -> 限流 -> 登录鉴权 + // 中间件顺序: 参数加解密 -> 安全校验 -> 限流 -> 登录鉴权 + api.Use(middleware.PayloadCryptoMiddleware()) api.Use(middleware.SecurityMiddleware()) api.Use(middleware.RateLimitMiddleware()) api.Use(middleware.AuthMiddleware()) diff --git a/server/middleware/payload_crypto.go b/server/middleware/payload_crypto.go new file mode 100644 index 0000000..de4925f --- /dev/null +++ b/server/middleware/payload_crypto.go @@ -0,0 +1,213 @@ +// Package middleware 参数加解密中间件 +package middleware + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "net/http" + "strings" + + "server/common" + "server/config" + + "github.com/gin-gonic/gin" +) + +// PayloadCryptoMiddleware 请求/响应参数加解密中间件 +func PayloadCryptoMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + cfg := config.AppConfig.PayloadCrypto + if !cfg.Enable { + c.Next() + return + } + + path := c.Request.URL.Path + if isPayloadCryptoWhitelist(path, cfg.Whitelist) { + c.Next() + return + } + + if strings.TrimSpace(cfg.SecretKey) == "" { + common.Warn("参数加密开启但secret_key为空,已跳过 Path=%s", path) + c.Next() + return + } + + if cfg.Request.Enable { + if err := maybeDecryptRequest(c, cfg); err != nil { + common.Warn("请求解密失败: %v Path=%s", err, path) + common.Error(c, http.StatusBadRequest, "请求解密失败") + c.Abort() + return + } + } + + if !cfg.Response.Enable { + c.Next() + return + } + + originWriter := c.Writer + writer := newCryptoResponseWriter(originWriter) + c.Writer = writer + + c.Next() + + c.Writer = originWriter + if err := writeEncryptedResponse(c, writer, cfg); err != nil { + common.Warn("响应加密失败: %v Path=%s", err, path) + writer.writePlain(originWriter) + } + } +} + +func maybeDecryptRequest(c *gin.Context, cfg config.PayloadCryptoConfig) error { + headerVal := strings.TrimSpace(c.GetHeader(cfg.HeaderKey)) + needDecrypt := headerVal != "" && headerVal != "0" && strings.ToLower(headerVal) != "false" + if cfg.Request.Required && !needDecrypt { + return errRequiredEncrypted + } + + if !needDecrypt { + return nil + } + + bodyBytes, err := io.ReadAll(c.Request.Body) + if err != nil { + return err + } + if len(bodyBytes) == 0 { + return errEmptyBody + } + + var payload common.EncryptedPayload + if err := json.Unmarshal(bodyBytes, &payload); err != nil { + return err + } + + plaintext, err := common.DecryptPayload(payload, cfg.SecretKey) + if err != nil { + return err + } + + c.Request.Body = io.NopCloser(bytes.NewReader(plaintext)) + c.Request.ContentLength = int64(len(plaintext)) + if c.Request.Header.Get("Content-Type") == "" { + c.Request.Header.Set("Content-Type", "application/json") + } + + return nil +} + +func writeEncryptedResponse(c *gin.Context, writer *cryptoResponseWriter, cfg config.PayloadCryptoConfig) error { + status := writer.Status() + if status == http.StatusNoContent || status == http.StatusNotModified { + writer.writePlain(c.Writer) + return nil + } + + contentType := c.Writer.Header().Get("Content-Type") + if contentType != "" && !strings.HasPrefix(contentType, "application/json") { + writer.writePlain(c.Writer) + return nil + } + + if writer.body.Len() == 0 { + writer.writePlain(c.Writer) + return nil + } + + payload, err := common.EncryptPayload(writer.body.Bytes(), cfg.SecretKey) + if err != nil { + return err + } + + respBytes, err := json.Marshal(payload) + if err != nil { + return err + } + + c.Writer.Header().Set(cfg.HeaderKey, "1") + c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8") + c.Writer.Header().Del("Content-Length") + c.Writer.WriteHeader(status) + _, err = c.Writer.Write(respBytes) + return err +} + +func isPayloadCryptoWhitelist(path string, whitelist []string) bool { + for _, white := range whitelist { + if len(path) >= len(white) && path[:len(white)] == white { + return true + } + } + return false +} + +type cryptoResponseWriter struct { + gin.ResponseWriter + body *bytes.Buffer + status int +} + +func newCryptoResponseWriter(w gin.ResponseWriter) *cryptoResponseWriter { + return &cryptoResponseWriter{ + ResponseWriter: w, + body: &bytes.Buffer{}, + status: http.StatusOK, + } +} + +func (w *cryptoResponseWriter) WriteHeader(code int) { + if code > 0 { + w.status = code + } +} + +func (w *cryptoResponseWriter) WriteHeaderNow() { + if w.status == 0 { + w.status = http.StatusOK + } +} + +func (w *cryptoResponseWriter) Write(data []byte) (int, error) { + return w.body.Write(data) +} + +func (w *cryptoResponseWriter) WriteString(s string) (int, error) { + return w.body.WriteString(s) +} + +func (w *cryptoResponseWriter) Status() int { + if w.status == 0 { + return http.StatusOK + } + return w.status +} + +func (w *cryptoResponseWriter) Size() int { + return w.body.Len() +} + +func (w *cryptoResponseWriter) Written() bool { + return w.body.Len() > 0 +} + +func (w *cryptoResponseWriter) Flush() {} + +func (w *cryptoResponseWriter) writePlain(origin gin.ResponseWriter) { + origin.Header().Del("Content-Length") + origin.WriteHeader(w.Status()) + if w.body.Len() == 0 { + return + } + _, _ = origin.Write(w.body.Bytes()) +} + +var ( + errRequiredEncrypted = errors.New("request body must be encrypted") + errEmptyBody = errors.New("request body is empty") +) diff --git a/server/modules/api/service/wechat_service.go b/server/modules/api/service/wechat_service.go index 1cd1d51..730e361 100644 --- a/server/modules/api/service/wechat_service.go +++ b/server/modules/api/service/wechat_service.go @@ -99,7 +99,7 @@ func (s *WechatMiniProgramService) Login(req *apiDto.WechatMiniLoginRequest) (*a Phone: phonePtr, Gender: 0, Status: 1, - Deleted: 0, + DelFlag: 0, } if phone != "" { salt := uuid.NewString()[:8] @@ -125,7 +125,7 @@ func (s *WechatMiniProgramService) Login(req *apiDto.WechatMiniLoginRequest) (*a PlatformSessionKey: session.SessionKey, PlatformExtra: req.PlatformExtra, LastLoginTime: &now, - Deleted: 0, + DelFlag: 0, } if err := s.platformUserMapper.Create(platform); err != nil { return nil, err diff --git a/server/modules/user/entity/platform_user.go b/server/modules/user/entity/platform_user.go index 71af688..9f42611 100644 --- a/server/modules/user/entity/platform_user.go +++ b/server/modules/user/entity/platform_user.go @@ -19,7 +19,7 @@ type PlatformUser struct { LastLoginTime *time.Time `gorm:"column:last_login_time;comment:最后登录时间" json:"lastLoginTime"` CreateTime time.Time `gorm:"column:create_time;autoCreateTime;comment:创建时间" json:"createTime"` UpdateTime time.Time `gorm:"column:update_time;autoUpdateTime;comment:更新时间" json:"updateTime"` - Deleted int8 `gorm:"column:deleted;comment:软删除:0-未删,1-已删" json:"deleted"` + DelFlag int8 `gorm:"column:del_flag;comment:软删除:0-未删,1-已删" json:"delFlag"` } // TableName 指定表名 diff --git a/server/modules/user/entity/user.go b/server/modules/user/entity/user.go index 3e06711..f2d9dcb 100644 --- a/server/modules/user/entity/user.go +++ b/server/modules/user/entity/user.go @@ -16,7 +16,7 @@ type User struct { Status int8 `gorm:"column:status;comment:状态:0-禁用,1-正常" json:"status"` CreateTime time.Time `gorm:"column:create_time;autoCreateTime;comment:创建时间" json:"createTime"` UpdateTime time.Time `gorm:"column:update_time;autoUpdateTime;comment:更新时间" json:"updateTime"` - Deleted int8 `gorm:"column:deleted;comment:软删除:0-未删,1-已删" json:"deleted"` + DelFlag int8 `gorm:"column:del_flag;comment:软删除:0-未删,1-已删" json:"delFlag"` } // TableName 指定表名 diff --git a/server/modules/user/mapper/platform_user_mapper.go b/server/modules/user/mapper/platform_user_mapper.go index 3f03fe0..f23c95e 100644 --- a/server/modules/user/mapper/platform_user_mapper.go +++ b/server/modules/user/mapper/platform_user_mapper.go @@ -22,7 +22,7 @@ func (m *PlatformUserMapper) baseDB() *gorm.DB { // GetDB 获取数据库实例,默认过滤软删除 func (m *PlatformUserMapper) GetDB() *gorm.DB { - return m.baseDB().Where("deleted = 0") + return m.baseDB().Where("delFlag = 0") } // FindAll 分页查询 @@ -73,5 +73,5 @@ func (m *PlatformUserMapper) UpdateFields(id int64, fields map[string]interface{ // Delete 逻辑删除 func (m *PlatformUserMapper) Delete(id int64) error { - return m.baseDB().Model(&entity.PlatformUser{}).Where("id = ?", id).Update("deleted", 1).Error + return m.baseDB().Model(&entity.PlatformUser{}).Where("id = ?", id).Update("delFlag", 1).Error } diff --git a/server/modules/user/mapper/user_mapper.go b/server/modules/user/mapper/user_mapper.go index 9f07336..c5cfccc 100644 --- a/server/modules/user/mapper/user_mapper.go +++ b/server/modules/user/mapper/user_mapper.go @@ -22,7 +22,7 @@ func (m *UserMapper) baseDB() *gorm.DB { // GetDB 获取数据库实例,默认过滤软删除 func (m *UserMapper) GetDB() *gorm.DB { - return m.baseDB().Where("deleted = 0") + return m.baseDB().Where("delFlag = 0") } // FindAll 分页查询 @@ -66,5 +66,5 @@ func (m *UserMapper) UpdateFields(id int64, fields map[string]interface{}) error // Delete 逻辑删除 func (m *UserMapper) Delete(id int64) error { - return m.baseDB().Model(&entity.User{}).Where("id = ?", id).Update("deleted", 1).Error + return m.baseDB().Model(&entity.User{}).Where("id = ?", id).Update("delFlag", 1).Error } diff --git a/server/modules/user/service/platform_user_service.go b/server/modules/user/service/platform_user_service.go index 1264af2..aa0b67a 100644 --- a/server/modules/user/service/platform_user_service.go +++ b/server/modules/user/service/platform_user_service.go @@ -37,7 +37,7 @@ func (s *PlatformUserService) Create(req *dto.CreatePlatformUserRequest) (*entit PlatformSessionKey: req.PlatformSessionKey, PlatformExtra: req.PlatformExtra, LastLoginTime: req.LastLoginTime, - Deleted: 0, + DelFlag: 0, } if err := s.mapper.Create(item); err != nil { return nil, err diff --git a/server/modules/user/service/user_service.go b/server/modules/user/service/user_service.go index 65a6467..ed60207 100644 --- a/server/modules/user/service/user_service.go +++ b/server/modules/user/service/user_service.go @@ -54,7 +54,7 @@ func (s *UserService) Create(req *dto.CreateUserRequest) (*entity.User, error) { Phone: phone, Gender: 0, Status: 1, - Deleted: 0, + DelFlag: 0, } if req.Gender != nil { user.Gender = *req.Gender diff --git a/task_detail_2026_03_16.md b/task_detail_2026_03_16.md new file mode 100644 index 0000000..da28eaa --- /dev/null +++ b/task_detail_2026_03_16.md @@ -0,0 +1,28 @@ +# 任务执行摘要 + +## 会话 ID: codex +- [2026-03-16 16:48:26] +- **执行原因**: 用户询问是否存在接口返回/请求参数加密配置。 +- **执行过程**: 1. 检索仓库内与加密/签名相关的配置与实现。 2. 查看安全校验中间件与配置文件,核对是否有参数加密配置。 +- **执行结果**: 未发现通用的请求/响应参数加密配置,仅存在签名校验配置及业务内的密码/微信数据加解密实现。 + +# 任务执行摘要 + +## 会话 ID: codex +- [2026-03-16 17:08:33] +- **执行原因**: 用户请求补充请求/响应参数加密的配置与控制能力。 +- **执行过程**: + 1. 新增 `payload_crypto` 配置结构与开发/测试/生产配置示例。 + 2. 增加 AES-GCM 加解密工具与参数加解密中间件,并接入路由中间件链。 +- **执行结果**: 已支持通过配置开关控制请求解密与响应加密(AES-GCM),并提供白名单与加密标记请求头。 + +# 任务执行摘要 + +## 会话 ID: codex +- [2026-03-16 17:13:59] +- **执行原因**: 用户要求输出前端对接参数加密的工作清单文档。 +- **执行过程**: + 1. 编写前端对接说明与任务清单(加密协议、触发规则、实现建议)。 + 2. 保存为独立 Markdown 文档供前端工程师使用。 +- **执行结果**: 已生成 `frontend_payload_crypto_tasks.md`。 +