前言
这篇文章主要记录学习黑马react文章发布功能,以及用go编写后端代码的过程。
一、学习经过
二、学习总结
1富文本编辑器Slate
听说这东西是一个深坑,我跳了两天没整明白,怕报错删成了最基础的组件。
子组件RichTextEditor
import React, { useState} from 'react';
import {createEditor, Editor, Transforms} from 'slate';
import { Slate, Editable, withReact } from 'slate-react';
//TODO 啥也不是,整个框架占位置,现在白板的跟text没区别
const RichTextEditor = ({value, onChange}) => {
// 创建一个 Slate 编辑器实例
const [editor] = useState(() => withReact(createEditor()))
return (
<Slate
editor={editor}
value={value}
onChange={(newValue) => onChange(newValue)}
initialValue={value}>
<Editable
placeholder="请输入内容..."
style={{ border: '1px solid #ccc', padding: '10px' }}
/>
</Slate>
);
};
export default RichTextEditor;
父组件中传入初始化内容,通过onChange获取修改后的值
initialValues={{
type: 0 ,
coverURL : '',
richText : [
{
type: 'paragraph',
children: [{ text: '开始输入你的文本...' }],
},
]}}
<Form.Item
label="内容"
name="richText"
rules={[{ required: true, message: '请输入文章内容' }]}
>
<RichTextEditor
value={form.getFieldValue('richText')}
onChange={(newValue)=> form.setFieldValue({richText : newValue})}/>
</Form.Item>
2 注意GORM的更新语句
GORM有两种更新方式,
- Save:此方法会更新记录的所有字段。无论你是否修改了某个字段,Save方法都会将结构体中的所有字段值更新到数据库对应的记录里。
return ur.db.Save(user).Error
- Updates:该方法仅更新你指定的字段。你能够选择性地更新部分字段,而不会影响其他字段。
return ur.db.Model(user).Updates(user).Error
需要格外注意的是,一般用ai生成的往往采用Save方式,但这种方式会导致当更新时间为空时,默认使用类型的零值做填充,而时间的零值会触发MySQL的报错,导致修改不成功。而使用Updates则不会有这种情况,Gin框架会自动管理更新时间为当前时间。
3 ant的Form组件带token的上传
不带token的话直接用action属性加请求地址
但如果要在请求头带上token的话,可以这样写,我的是拆分到了各模块里,看起来优雅点,具体可以去看我的项目源码
// 上传图片
export function uploadImageAPI(file) {
const formData = new FormData();
formData.append('image', file);
return request({
url: '/upload', // 根据实际接口调整
method: 'post',
data: formData,
});
}
// 封装上传处理函数
const handleUpload = async ({ file, onSuccess, onError }) => {
try {
const res = await uploadImageAPI(file);
onSuccess(res.data); // 通知Upload组件上传成功
// 将返回的图片URL存入表单(根据实际接口结构调整)
form.setFieldsValue({ coverURL: res.data.url });
} catch (error) {
onError(error); // 通知Upload组件上传失败
if (error.response && error.response.status === 400) {
console.error('上传失败:', error.response.data);
} else {
console.error('其他错误:', error);
}
}
};
<Upload
listType="picture-card"
showUploadList
name = "image"
onChange={onChange}
customRequest={handleUpload}
>
<div style={{ marginTop: 8 }}>
<PlusOutlined />
</div>
</Upload>
4 go集成阿里云oss服务器
初始化oss并放入全局变量方便取用
// 初始化OSS客户端
func InitOSS() {
// 获取OSS配置
accessKeyID := global.Cfg.OSSAccessKeyId
accessKeySecret := global.Cfg.OSSAccessKeySecret
endpoint := global.Cfg.OSSEndpoint
bucketName := global.Cfg.OSSBucketName
// 初始化OSS客户端
client, err := oss.New(endpoint, accessKeyID, accessKeySecret)
if err != nil {
global.Logger.Fatalf("无法初始化OSS客户端: %v", err)
}
// 获取存储桶
global.OSSBucket, err = client.Bucket(bucketName)
if err != nil {
global.Logger.Fatalf("无法获取OSS存储桶: %v", err)
}
}
访问oss服务器进行文件上传的操作进行封装
// 文件上传
func UploadFile(file *multipart.FileHeader) (string, error) {
src, err := file.Open()
if err != nil {
global.Logger.Println("无法打开文件: %v", err)
return "", err
}
defer src.Close()
// 指定文件夹名,项目统一上传到指定文件夹下
folderName := "GIN-API/"
fileName := folderName + file.Filename
err = global.OSSBucket.PutObject(fileName, src)
if err != nil {
global.Logger.Println("无法上传文件到OSS: %v", err)
return "", err
}
// 返回文件URL
url := "https://" + global.Cfg.OSSBucketName + "." + global.Cfg.OSSEndpoint + "/" + fileName
return url, nil
}
图片上传的具体handler逻辑
func UploadImage(c *gin.Context) {
// 获取上传的图片文件
file, err := c.FormFile("image")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "上传文件失败"})
global.Logger.Println("上传文件失败: %v", err)
return
}
// 穷的慌怕上传大了
if file.Size > 1024*1024*2 {
c.JSON(http.StatusBadRequest, gin.H{"error": "上传文件大小不能超过2MB"})
return
}
url, err := utils.UploadFile(file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "上传文件失败"})
return
}
c.JSON(http.StatusOK, gin.H{"url": url})
}
整体项目架构可以看后端项目源码
总结
前端与后端的开发是两种不太一样的思维方式,后端采取分层的思维方式,每一层都只完成特定的功能,然后逐层传递,相互调用。而前端往往由一个个组件构成,相对于前端要更为松散,颗粒度也更细,也有可能是我前端学习不够,没法拥有一种宏观的视觉所导致的。此外全栈的开发有助于对问题的定位,出bug的时候可以拥有多种手段定位到问题所在,方便对bug进行打印调试。