feat: 调整客户信息表删除字段为del_dlag

This commit is contained in:
zwt13703 2026-03-22 13:54:15 +08:00
parent 433b4469de
commit f7cb916e01
20 changed files with 505 additions and 29 deletions

View File

@ -9,7 +9,7 @@ CREATE TABLE t_platform_user (
last_login_time TIMESTAMP NULL COMMENT '最后登录时间', last_login_time TIMESTAMP NULL COMMENT '最后登录时间',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE 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), 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 CONSTRAINT fk_platform_user_user_id FOREIGN KEY (user_id) REFERENCES t_user(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台用户关联表(微信/抖音小程序用户信息)'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台用户关联表(微信/抖音小程序用户信息)';

View File

@ -10,5 +10,5 @@ CREATE TABLE t_user (
status TINYINT DEFAULT 1 COMMENT '状态0-禁用1-正常', status TINYINT DEFAULT 1 COMMENT '状态0-禁用1-正常',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE 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='用户基础信息表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户基础信息表';

View File

@ -9,7 +9,7 @@ CREATE TABLE t_platform_user (
last_login_time TIMESTAMP, last_login_time TIMESTAMP,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT DEFAULT 0, delFlag SMALLINT DEFAULT 0,
UNIQUE (platform_type, platform_openid), UNIQUE (platform_type, platform_openid),
CONSTRAINT fk_platform_user_user_id FOREIGN KEY (user_id) REFERENCES t_user(id) ON DELETE CASCADE 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.last_login_time IS '最后登录时间';
COMMENT ON COLUMN t_platform_user.create_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.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() CREATE OR REPLACE FUNCTION set_update_time()
RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$

View File

@ -10,7 +10,7 @@ CREATE TABLE t_user (
status SMALLINT DEFAULT 1, status SMALLINT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT DEFAULT 0, delFlag SMALLINT DEFAULT 0,
UNIQUE (phone) 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.status IS '状态0-禁用1-正常';
COMMENT ON COLUMN t_user.create_time IS '创建时间'; COMMENT ON COLUMN t_user.create_time IS '创建时间';
COMMENT ON COLUMN t_user.update_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() CREATE OR REPLACE FUNCTION set_update_time()
RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$

View File

@ -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直接走原逻辑。
- 与后端保持兼容:后端若未开启加密,不应影响现有请求。
## 五、实现建议
- 统一封装在请求拦截器/响应拦截器层:
- WebAxios 拦截器。
- 小程序:封装 `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 响应不应被尝试解密。

View File

@ -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
}

View File

@ -13,6 +13,19 @@ security:
header_key: X-App-Sign header_key: X-App-Sign
secret_key: yts@2025#secure 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: rate_limit:
enable: true enable: true
default: default:
@ -42,9 +55,9 @@ database:
host: 10.13.13.1 host: 10.13.13.1
#port: 3306 #port: 3306
port: 5432 port: 5432
database: fast-common-db database: wz-db
username: user_3W72AM username: wz-db
password: "password_KAwdZW" password: "sYpphaZWYpEEtrS7"
charset: utf8mb4 charset: utf8mb4
max_idle_conns: 20 max_idle_conns: 20
max_open_conns: 100 max_open_conns: 100
@ -66,6 +79,7 @@ wechat:
app_secret: "ed3fd9089dcfbd1d886eddeca69c07bd" app_secret: "ed3fd9089dcfbd1d886eddeca69c07bd"
app_config: app_config:
tenantId: 000000
app: app:
min_version: "1.2.0" min_version: "1.2.0"
latest_version: "1.3.5" latest_version: "1.3.5"

View File

@ -12,15 +12,16 @@ import (
var AppConfig = &appConfig{} var AppConfig = &appConfig{}
type appConfig struct { type appConfig struct {
Log LogConfig `yaml:"log"` Log LogConfig `yaml:"log"`
Server ServerConfig `yaml:"server"` Server ServerConfig `yaml:"server"`
Security SecurityConfig `yaml:"security"` Security SecurityConfig `yaml:"security"`
RateLimit RateLimitConfig `yaml:"rate_limit"` PayloadCrypto PayloadCryptoConfig `yaml:"payload_crypto"`
Swagger SwaggerConfig `yaml:"swagger"` RateLimit RateLimitConfig `yaml:"rate_limit"`
Database DatabaseConfig `yaml:"database"` Swagger SwaggerConfig `yaml:"swagger"`
Redis RedisConfig `yaml:"redis"` Database DatabaseConfig `yaml:"database"`
Wechat WechatConfig `yaml:"wechat"` Redis RedisConfig `yaml:"redis"`
AppConfig AppVersionConfig `yaml:"app_config"` Wechat WechatConfig `yaml:"wechat"`
AppConfig AppVersionConfig `yaml:"app_config"`
} }
// LogConfig 日志配置 // LogConfig 日志配置
@ -44,6 +45,22 @@ type SecurityConfig struct {
SecretKey string `yaml:"secret_key"` // 签名密钥 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 限流配置 // RateLimitConfig 限流配置
type RateLimitConfig struct { type RateLimitConfig struct {
Enable bool `yaml:"enable"` // 是否启用 Enable bool `yaml:"enable"` // 是否启用
@ -104,6 +121,7 @@ type AppVersionConfig struct {
TTLSeconds int `yaml:"ttl_seconds"` TTLSeconds int `yaml:"ttl_seconds"`
Disabled bool `yaml:"disabled"` Disabled bool `yaml:"disabled"`
DisableReason string `yaml:"disable_reason"` DisableReason string `yaml:"disable_reason"`
TenantId string `yaml:"tenantId"`
} }
// AppClientConfig 客户端版本配置 // AppClientConfig 客户端版本配置

View File

@ -13,6 +13,19 @@ security:
header_key: X-App-Sign header_key: X-App-Sign
secret_key: yts@2025#secure 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: rate_limit:
enable: true enable: true
default: default:

View File

@ -13,6 +13,19 @@ security:
header_key: X-App-Sign header_key: X-App-Sign
secret_key: yts@2025#secure 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: rate_limit:
enable: true enable: true
default: default:

View File

@ -85,7 +85,8 @@ func main() {
// API 路由组 // API 路由组
api := r.Group("/api") api := r.Group("/api")
// 中间件顺序: 安全校验 -> 限流 -> 登录鉴权 // 中间件顺序: 参数加解密 -> 安全校验 -> 限流 -> 登录鉴权
api.Use(middleware.PayloadCryptoMiddleware())
api.Use(middleware.SecurityMiddleware()) api.Use(middleware.SecurityMiddleware())
api.Use(middleware.RateLimitMiddleware()) api.Use(middleware.RateLimitMiddleware())
api.Use(middleware.AuthMiddleware()) api.Use(middleware.AuthMiddleware())

View File

@ -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")
)

View File

@ -99,7 +99,7 @@ func (s *WechatMiniProgramService) Login(req *apiDto.WechatMiniLoginRequest) (*a
Phone: phonePtr, Phone: phonePtr,
Gender: 0, Gender: 0,
Status: 1, Status: 1,
Deleted: 0, DelFlag: 0,
} }
if phone != "" { if phone != "" {
salt := uuid.NewString()[:8] salt := uuid.NewString()[:8]
@ -125,7 +125,7 @@ func (s *WechatMiniProgramService) Login(req *apiDto.WechatMiniLoginRequest) (*a
PlatformSessionKey: session.SessionKey, PlatformSessionKey: session.SessionKey,
PlatformExtra: req.PlatformExtra, PlatformExtra: req.PlatformExtra,
LastLoginTime: &now, LastLoginTime: &now,
Deleted: 0, DelFlag: 0,
} }
if err := s.platformUserMapper.Create(platform); err != nil { if err := s.platformUserMapper.Create(platform); err != nil {
return nil, err return nil, err

View File

@ -19,7 +19,7 @@ type PlatformUser struct {
LastLoginTime *time.Time `gorm:"column:last_login_time;comment:最后登录时间" json:"lastLoginTime"` LastLoginTime *time.Time `gorm:"column:last_login_time;comment:最后登录时间" json:"lastLoginTime"`
CreateTime time.Time `gorm:"column:create_time;autoCreateTime;comment:创建时间" json:"createTime"` CreateTime time.Time `gorm:"column:create_time;autoCreateTime;comment:创建时间" json:"createTime"`
UpdateTime time.Time `gorm:"column:update_time;autoUpdateTime;comment:更新时间" json:"updateTime"` 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 指定表名 // TableName 指定表名

View File

@ -16,7 +16,7 @@ type User struct {
Status int8 `gorm:"column:status;comment:状态0-禁用1-正常" json:"status"` Status int8 `gorm:"column:status;comment:状态0-禁用1-正常" json:"status"`
CreateTime time.Time `gorm:"column:create_time;autoCreateTime;comment:创建时间" json:"createTime"` CreateTime time.Time `gorm:"column:create_time;autoCreateTime;comment:创建时间" json:"createTime"`
UpdateTime time.Time `gorm:"column:update_time;autoUpdateTime;comment:更新时间" json:"updateTime"` 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 指定表名 // TableName 指定表名

View File

@ -22,7 +22,7 @@ func (m *PlatformUserMapper) baseDB() *gorm.DB {
// GetDB 获取数据库实例,默认过滤软删除 // GetDB 获取数据库实例,默认过滤软删除
func (m *PlatformUserMapper) GetDB() *gorm.DB { func (m *PlatformUserMapper) GetDB() *gorm.DB {
return m.baseDB().Where("deleted = 0") return m.baseDB().Where("delFlag = 0")
} }
// FindAll 分页查询 // FindAll 分页查询
@ -73,5 +73,5 @@ func (m *PlatformUserMapper) UpdateFields(id int64, fields map[string]interface{
// Delete 逻辑删除 // Delete 逻辑删除
func (m *PlatformUserMapper) Delete(id int64) error { 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
} }

View File

@ -22,7 +22,7 @@ func (m *UserMapper) baseDB() *gorm.DB {
// GetDB 获取数据库实例,默认过滤软删除 // GetDB 获取数据库实例,默认过滤软删除
func (m *UserMapper) GetDB() *gorm.DB { func (m *UserMapper) GetDB() *gorm.DB {
return m.baseDB().Where("deleted = 0") return m.baseDB().Where("delFlag = 0")
} }
// FindAll 分页查询 // FindAll 分页查询
@ -66,5 +66,5 @@ func (m *UserMapper) UpdateFields(id int64, fields map[string]interface{}) error
// Delete 逻辑删除 // Delete 逻辑删除
func (m *UserMapper) Delete(id int64) error { 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
} }

View File

@ -37,7 +37,7 @@ func (s *PlatformUserService) Create(req *dto.CreatePlatformUserRequest) (*entit
PlatformSessionKey: req.PlatformSessionKey, PlatformSessionKey: req.PlatformSessionKey,
PlatformExtra: req.PlatformExtra, PlatformExtra: req.PlatformExtra,
LastLoginTime: req.LastLoginTime, LastLoginTime: req.LastLoginTime,
Deleted: 0, DelFlag: 0,
} }
if err := s.mapper.Create(item); err != nil { if err := s.mapper.Create(item); err != nil {
return nil, err return nil, err

View File

@ -54,7 +54,7 @@ func (s *UserService) Create(req *dto.CreateUserRequest) (*entity.User, error) {
Phone: phone, Phone: phone,
Gender: 0, Gender: 0,
Status: 1, Status: 1,
Deleted: 0, DelFlag: 0,
} }
if req.Gender != nil { if req.Gender != nil {
user.Gender = *req.Gender user.Gender = *req.Gender

28
task_detail_2026_03_16.md Normal file
View File

@ -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`