django之Formh与ModelForm
1.Form和ModelForm组件
组件可以用来干嘛
- 表单验证(对用户提交的数据进行校验)
- 自动生成HTML标签【能够将自定义的组件和相对应的类作关联】
- 数据初始化(新建按钮、编辑按钮)、保持原来的数据。
保持原来的数据是什么意思呢?
比如我们使用用户名和密码去登录一个网站,当密码错误之后,上一次填写的用户名和密码立马被清空,这个时候,一定会让我们很恼火,我密码错了,好家伙,直接把我用户名数据干没了,要是我,不注册了哈哈!
1.1 Form组件
以python开发为例:
Django框架:含内置+第三方表单验证组件
Falsk框架:无内置,只能用第三方表单验证组件。eg:wtforms
-
表单验证组件化 保持原来的数据 【优点在于把所有的校验都放到form组件中】eg:
# 自定义form组件的类(表单类)【重点要会类怎么写,参数各式什么意思,就能实现表单的 验证】 from django.shortcuts import render,HttpResponse from django import forms class MyForm(Form): mobile1 = forms.CharField(reg="\d{11}", required=True,widget=forms.TextInput) sms = forms.CharField(required=True) """ 用户注册 """ if request.method == "GET": # 用户向网站发送请求 True -> 让用户看到页面 # 实例化对象并初始化数据 form = MyForm(initial={"mobile1":"18766666666","sms":"999"}) # 将表单实例form传递给xx.html模版,并渲染该模版 return render(request, "xx.html",{"form":form}) # request.POST -> 包含用户提交的所有数据 #将用户提交的数据给到Myform组件,该组件根据定义字段去用户提交数据中提取数据,最后根据内部定义的校验机制进行校验 form = MyForm(request.POST) if form.is_valid(): # 开始校验 print(form.cleared_data) # 获取到校验成功的数据 else: print(form.errors) # 获取到校验失败的数据 # 保持原来的数据 return render(request, "xxxxx.html",{"form":form})
-
生成HTML标签+初始化表单值
# 前端和后端的两种做法 # 前端的做法 <form method="post"> <input type="text" name="mobile1" /> <input type="text" name="gaojie" /> <input type="submit" value="提交" /> </form> ************************************************************ # 后端的做法 <form method="post"> # 自动生成标签 【初始化值在业务逻辑中实例化form组件的类中的 instance参数】 {{form.mobile1}} # 通过widget插件生成标签 {{form.gaojie}} <input type="submit" value="提交" /> </form>
注意:用户点击按钮提交数据一定要加{% csrf_token %}
1.1.2 form组件生成默认值
- 给单个字段生成默认值【定义字段时初始化】
class MyForm(Form): username = forms.CharField(required=True,initial="默认值") email = forms.CharField(required=True,initial="默认值")
- 批量生成默认值【实例化form对象时初始化】
form = UserForm(initial={“username”:"张欣怡","email":"843615824@qq.com"})
1.1.3 验证规则
- 内置
required=True
- 自定义验证规则【有两种】
1.正则表达式
2.钩子方法【Hook方法】from django.core.validators import RegexValidator class UserForm(forms.Form): user = forms.CharField(label="用户名", max_length=32) pwd = forms.CharField(label="密码", max_length=32) mobile = forms.IntegerField( label="手机号", required=True, validators=[RegexValidator(r'^\d{11}$', "手机号格式错误"), ],), # 正则校验 widget=forms.TextInput,
from django.core.validators import RegexValidator from django.core.exceptions import ValidationError class UserForm(forms.Form): user = forms.CharField(label="用户名", max_length=32) pwd = forms.CharField(label="密码", max_length=32) mobile = forms.IntegerField( label="手机号", required=True, validators=[RegexValidator(r'^\d{11}$', "手机号格式错误"), ],), widget=forms.TextInput, # 当内置校验规则和正则校验完成之后执行钩子方法 def clean_mobile(self): # 通过前两种校验规则的数据会放到cleaned_data中,比如我们拿出来去数据库进行校验; value = self.cleaned_data["mobile"] """比如将用户的手机号和数据库中的手机号进行校验""" # 不存在,触发异常 raise ValidationError("异常信息")
注意
假设我们定义了内置校验required=True
以及正则定义规则
以及钩子方法
等三种定义规则,那么当用户点击按钮提交数据时,
假设其中的一种校验规则没有通过,那么就不会执行其他的校验规则
比如:用户名设定了required=True
【表示必填】规则,但当用户没有填写用户名,填写完其他的表单之后,即使点击了提交按钮,required=True
开始校验就没通过,那么接下里的校验也不会执行,更别说if form.is_valid():
中的 逻辑代码了,因为if form.is_valid():
的执行才是各个字段校验的开始
其他注意点
- 1.用户要提交数据,必须要有
{% csrf_token %}
eg: <form method="post"> {% csrf_token %} {{ form.username }} {{ form.pwd }} <input type="submit" value="提交"> </form>
- 2.用户提交后数据,通过
request.POST
获取 - 3.required = True
意味游览器会帮我们校验对应表单,如果没有写对应的数据,游览器就会检测出来,并不会
向后台发送网络请求
如果不想游览器对表单中的数据进行必填校验,应使用novalidate
假设:1.设定了required = True,当对应表单未填<form method="post" novalidate> {% csrf_token %} {{ form.username }} {{ form.pwd }} <input type="submit" value="提交"> </form>
- 4.校验成功后的值和未成功的值如何取呢?
def login(request): if request.method == 'GET': form = LoginForm() return render(request, "login.html", {"form": form}) # 将用户提交给你的数据交给你定义的组件,组件根据自己字段去提取数据,最后调用内部的required=True机制对他生成校验 form = LoginForm(data=request.POST) if form.is_valid(): print(form.cleaned_data) # 获取校验成功后的值 return HttpResponse("成功") else: print(form.errors) # 获取报错信息 return HttpResponse("失败")
- 5.内置的错误提示修改为中文
settings.LANGUAGE_CODE = 'zh-hans' # 中文 settings.LANGUAGE_CODE = 'en-us' # 英文
- 6.生成的html有默认的id名,为
id_字段名
,修改的方法如下widget=forms.TextInput(attrs={"id":"phone_id"})
1.1.4 错误信息
错误数据怎么来的
form.errors
的理解
if form.is_valid(): # 根据校验规则对每个字段进行校验
pass
如果有错误信息就会把它放到自己字段的errors中,最后校验完之后,把所有的errors再汇总到form.errors中
简单示例:
urls
from django.urls import path
from api import views
urlpatterns = [
path("login/",views.login)
]
views
from django.shortcuts import render, HttpResponse
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django import forms
class UserForm(forms.Form):
user = forms.CharField(label="用户名", max_length=32, required=True, widget=forms.TextInput)
pwd = forms.CharField(label="密码", max_length=32, widget=forms.PasswordInput)
mobile = forms.CharField(
label="手机号",
required=True,
validators=[RegexValidator(r'^\d{11}$', "手机号格式错误")],
widget=forms.TextInput
)
def clean_mobile(self):
# 通过前两种校验规则的数据会放到cleaned_data中,比如我们拿出来去数据库进行校验;
value = self.cleaned_data["mobile"]
"""比如将用户的手机号和数据库中的手机号进行校验"""
# 不存在,触发异常
raise ValidationError("异常信息")
def login(request):
# 1.用户请求进来
if request.method == "GET":
# 实例化form组件对象
form = UserForm()
# 给用户返回相应的页面,并将form对象相应的传递过去
return render(request, "login.html", {"form": form})
form = UserForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
return HttpResponse("登录成功")
else:
print(form.errors)
return render(request, "login.html", {"form": form}) # 此时校验失败,但是form组件中已经有了提交的数据
templates/login.html
<div>
<h1>用户登录</h1>
<form method="post" novalidate>
{% csrf_token %}
# form.字段 -> 标签控件【根据widget插件生成】,本质上是对象
# form.字段.errors -> 该字段校验的失败的错误信息【列表】
{{ form.user.label }}{{ form.user }}{{ form.user.errors.0 }} # 拿取user字段校验错误列表中的第一个信息
{{ form.pwd.label }}{{ form.pwd }}{{ form.pwd.errors.0 }} # 拿取pwd字段校验错误列表中的第一个信息
{{form.mobile.label}}{{form.mobile}}{{form.mobile.errors.0}}#拿取mobile字段校验错误列表中的第一个信息
<input type="submit" value="提交">
</form>
</div>
form.erors
输出到底是什么呢
- 表面是标签
模版渲染时,在html中1.当每个字段进行校验的时候,如果报错,会将错误信息构成字典,放到每个字段的errors中 eg:user字段报错 -> {“user”:["xx错误"]} eg: pwd字段报错 -> {"pwd":["xxx错误"]} 2.最后每个字段校验完成后会把所有的错误在放到errors字段中 errors = {“user”:["xx错误"],"pwd":["xxx错误"]} 解释说明键对应的值为什么列表 因为validators中里面可以定义多个校验规则,可能会有多个错误信息,所以是列表
- 真正是个对象嘿嘿,怎么看呢?
print(type(form.errors)) # <class 'django.forms.utils.ErrorDict'> 那我们就可以 from django.forms.utils import ErrorDict
这个在循环生成所有的字段就能体现出来了哦!
1.2 展示所有的字段
1.2.1 手动生成
class UserForm(forms.Form):
user = forms.CharField(label="用户",required=True,widget=forms.TextInput)
pwd = forms.CharField(label="密码", max_length=32, widget=forms.PasswordInput)
phone = forms.CharField(
label="手机号",
required=True,
validators=[RegexValidator(r'^\d{11}$', "手机号格式错误"), ],
widget=forms.TextInput(attrs={"id": "phone_id"}),
)
{{ form.user.label }}{{ form.user }}{{ form.user.errors.0 }}
{{ form.pwd.label }}{{ form.pwd }}{{ form.pwd.errors.0 }}
{{form.mobile.label}}{{form.mobile}}{{form.mobile.errors.0}}
1.2.2 循环生成
涉及到对象和可迭代对象。
class UserForm(forms.Form):
user = forms.CharField(label="用户",required=True,widget=forms.TextInput)
pwd = forms.CharField(label="密码", max_length=32, widget=forms.PasswordInput)
phone = forms.CharField(
label="手机号",
required=True,
validators=[RegexValidator(r'^\d{11}$', "手机号格式错误"), ],
widget=forms.TextInput(attrs={"id": "phone_id"}),
)
item->每个字段 item.errors -> 每个字段中对应的列表【错误信息】 item.errors.0 -> 每个字段中对应列表中的第一个错误信息
item.label -> 动态生成字段标签 item -> 标签控件 item.errors.0 -> 每个字段对应的第一条错误信息
{% for item in form %}
{{ item.label }}{{ item }}{{ item.errors.0 }}
{% endfor %}
手动&自动显示字段的难点
相同点
form.字段 = item
form.字段.label = item.label
form.字段.errors = item.errors
再来简单看看不同
手动生成字段
{{ form.user.label }}{{ form.user }}{{ form.user.errors.0 }}
{{ form.pwd.label }}{{ form.pwd }}{{ form.pwd.errors.0 }}
{{form.mobile.label}}{{form.mobile}}{{form.mobile.errors.0}}
自动生成字段
{% for item in form %}
{{ item.label }}{{ item }}{{ item.errors.0 }}
{% endfor %}
1.3 样式相关
光目前动态生成的字段多少有点丑,那我们就可以添加一些前端框架的属性让我们的页面变的更加好看!
样式嘛,本质上都是在给widget
插件中添加attrs
属性。eg:
widget=forms.TextInput(attrs={"class":"form-control"}) # <input type="text" class="form-control"/>
-
手动操作【给特定字段添加attrs属性】
class UserForm(forms.Form): user = forms.CharField( label="用户名", max_length=32, required=True, widget=forms.TextInput(attrs={"class": "form-control"}) ) pwd = forms.CharField( label="密码", max_length=32, widget=forms.PasswordInput(attrs={"class": "form-control"}) ) mobile = forms.CharField( label="手机号", required=True, validators=[RegexValidator(r'^\d{11}$', "手机号格式错误")], widget=forms.TextInput(attrs={"class":"form-control"}) ) ... .... .....
{% for field in form %} <p>{{ field.label }} {{ field }} {{ field.errors.0 }} </p> {% endfor %}
-
自动操作(找到每个字段中的widget插件,再找到插件中的attrs属性,进行
更新{"class":"form-control"}
注意field.widget.attrs = ({"class":"form-control"})
# 这个是重新赋值【会把原来的attrs属性给覆盖掉】field.widget.attrs.update({"class":"form-control"})
# 这个才是更新,相同键不同值就更新值,没有就直接添加class BaseForm(RenderableFormMixin): def __init__(): # 构造字典{"v1":对象,"v2":对象} # {"v1":forms.CharField(...,widget=forms.TextInput),"v2":.....对象} self.fields = copy.deepcopy() class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass): pass class UserForm(forms.Form): user = forms.CharField( label="用户名", max_length=32, required=True, widget=forms.TextInput ) pwd = forms.CharField( label="密码", max_length=32, widget=forms.PasswordInput ) mobile = forms.CharField( label="手机号", required=True, validators=[RegexValidator(r'^\d{11}$', "手机号格式错误")], widget=forms.TextInput ) def __init__(self,*args,**kwargs): # 通过执行BaseForm类中的__init__方法实现 super().__init__(self,*args,**kwargs) # 此时父类中就有self.fields = {} # 稍微作了一些条件的限制 ,当然我只是写了示例,可以自定义的! for name,field in self.fields.items(): if name == "user": # 如果是user字段就不给加form-control continue if field.widget.attrs: # 有attrs属性就给做修改 field.widget.attrs.update({"class":"form-control"}) # 做修改 else: # 没有就赋值 field.widget.attrs = {"class":"form-control"} # 覆盖原来的
form = RegisterForm() # __init__ 实例化 form = RegisterForm(data=request.POST) # __init__ 实例化
上面就是对展示所有字段的两种方式的介绍
但是,就下面这段代码这么写,每次,都要深浅拷贝就很烦,所以我搞了一个通用的父类,拿过来就能使用
def __init__(self,*args,**kwargs):
# 通过执行BaseForm类中的__init__方法实现
super().__init__(self,*args,**kwargs) # 此时父类中就有self.fields = {}
# 稍微作了一些条件的限制 ,当然我只是写了示例,可以自定义的!
for name,field in self.fields.items():
if name == "user": # 如果是user字段就不给加form-control
continue
if field.widget.attrs: # 有attrs属性就给做修改
field.widget.attrs.update({"class":"form-control"}) # 做修改
else: # 没有就赋值
field.widget.attrs = {"class":"form-control"} # 覆盖原来的
1.4 通用父类
- 推荐
它的思路class BootStrapForm(object): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs = {"class": "form-control"} class LoginForm(BootStrapForm, forms.Form): user = forms.CharField(label="用户名", widget=forms.TextInput) pwd = forms.CharField(label="密码", widget=forms.PasswordInput) def login(request): form = LoginForm() # 实例化组件对象 return render(request, "login.html", {"form": form})
1.当实例化组件的时候,自动先执行BootStrapForm的__init__方法, 2.然后里面再super【按照mro的继承关系继续向上寻找上一级】 3.将所有的键和字段对象打包成字典赋值给self.fields,即 self.fields = { "user":forms.CharField(.......widget=forms.TextInput), "pwd":forms.CharField(.......widget=forms.PasswordInput), "mobile":forms.CharField(.......widget=forms.TextInput) }
- 不推荐(多了一个继承关系)
class BootStrapForm(forms.Form): def __init__(self, *args, **kwargs): # 不是找父类 # 根据类的mro(继承关系),去找上个类 # super().__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs = {"class": "form-control"} class LoginForm(BootStrapForm): user = forms.CharField(label="用户名", widget=forms.TextInput) pwd = forms.CharField(label="密码", widget=forms.TextInput) def login(request): form = LoginForm() return render(request, "login.html", {"form": form})
这块呢,又涉及到类的内部继承关系,来了解一下这个到底是个什么东西!
1.5 类的继承关系
类的内部继承关系,是基于c3算法来玩的。
class A:
super().__init__() # 就会调用<class '__main__.object'>的__init__方法
class B:
super().__init__() # 就会调用<class '__main__.A'>的__init__方法
class C(B,A):
super().__init__() # 就会调用<class '__main__.B'>的__init__方法
# [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
print(C.mro())
1.6 登录页面
知识点:基于Form组件 + 自定义的通用父类 + Boostarp cdn
Boostarp cdn 的网址
实现页面如下
实现代码
-
urls
from django.urls import path from api import views urlpatterns = [ path("login/",views.login) ]
-
views
from django import forms from django.shortcuts import render, HttpResponse class BootstarpForm(object): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): field.widget.attrs.update({"class": "form-control"}) class LoginForm(BootstarpForm, forms.Form): user = forms.CharField( label="用户名", required=True, max_length=32, widget=forms.TextInput) pwd = forms.CharField( label="密码", required=True, max_length=32, widget=forms.TextInput ) def login(request): if request.method == 'GET': form = LoginForm() return render(request, "login.html", {"form": form}) form = LoginForm(request.POST) if form.is_valid(): return HttpResponse("登录成功") else: print(form.errors) return render(request, "login.html", {"form": form})
-
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.css" rel="stylesheet"> </head> <body> <div style="width: 300px;margin: 100px auto"> <h1>用户登录</h1> <form method="post" novalidate> {% csrf_token %} {% for item in form %} {{ item.label }}{{ item }}{{ item.errors.0 }} {% endfor %} <input type="submit" value="提交"> </form> </div> </body> </html>
1.2ModelForm组件
Form和ModelForm的区别【也是最明显的区别】
Form:需要用手动生成字段
ModelForm:基于model的字段自动生成form中的字段
使用form
- 创建Form类 + 定义字段
from django import forms class LoginForm(forms.Form): user = forms.CharField(label="用户名", widget=forms.TextInput) pwd = forms.CharField(label="密码", widget=forms.TextInput)
- 视图
def login(request): if request.method == "GET": form = LoginForm() return render(request, "login.html", {"form": form}) form = LoginForm(data=request.POST) if not form.is_valid(): # 校验失败 return render(request, "login.html", {"form": form}) print(form.cleaned_data) # ... return HttpRespon("OK")
- 前端
<form method="post" novalidate> {% csrf_token %} {% for field in form %} <p>{{ field.label }} {{ field }} {{ field.errors.0 }}</p> {% endfor %} <input type="submit" value="提交"> </form>
使用ModelForm
- 创建Form类 + 定义字段
from django import forms class LoginForm(forms.Form): user = forms.CharField(label="用户名", widget=forms.TextInput) pwd = forms.CharField(label="密码", widget=forms.TextInput)
- 通过orm类,创建相应的表和字段【执行下面的命令】
python manage.py makemigrations python manage.py migrate
- 视图 【创建ModelForm + 业务逻辑】
class LoginForm(forms.ModelForm): # 可以自定义字段 mobile = forms.CharFiled(label="手机号") #如果要校验,就要重写数据中生成的字段 mobile = forms.CharFiled(label="手机号",validators=[RegexValidator(r'^\d{11}$', "手机号格式错误")]) # 直接从数据库中拿取生成字段 class Meta: model = models.UserInfo fileds = ["name","age", "mobile"] # 定义插件 -> 指定字段使用的 HTML 控件类型 widgets = { "name":forms.TextInput, "age":forms.TextInput, } # 更改字段的标签名称 labels ={ "name":"v1", "age":"age1", }
- 前端
<form method="post" novalidate> {% for field in form %} {% csrf_token %} <p>{{ field.label }} {{ field }} {{ field.errors.0 }}</p> <input type="submit" value="提交"> {% endfor %} </form>
注意:
- 后续进行增删改查是基于数据库Models中的某个表,推荐使用:ModelForm;
- 如果要进行表单校验是与数据库的表无关直接使用Form。
1.2.1 ModelForm比较好的地方
一 初始化数据
- form
class LoginForm(BootStrapForm, forms.Form): user = forms.CharField(label="用户名", widget=forms.TextInput) pwd = forms.CharField(label="密码", widget=forms.TextInput)
def login(request): if request.method == "GET": form = LoginForm(initial={"user": "老高", "pwd": "123"}) return render(request, "login.html", {"form": form})
- ModelForm
class User(models.Model): username = models.CharField(verbose_name="用户名", max_length=32) age = models.IntegerField(verbose_name="年龄")
class LoginModelForm(BootStrapForm, forms.ModelForm): mobile = forms.CharField(label="手机号", widget=forms.TextInput) class Meta: model = models.UserInfo fields = ["username", "age", "mobile"] widgets = { "age": forms.TextInput, # 可以修改age对应的插件 } labels = { "age": "x2", # 可以修改age在页面上展示的label } # 钩子方法 def clean_name(self): value = self.cleaned_data['name'] # raise ValidationError("....") return value
def login(request): user_object = models.UserInfo.objects.filter(id=1).first() form = LoginModelForm(instance=user_object, initial={"mobile": "老高"}) return render(request, "login.html", {"form": form})
二.新建数据
- form组件
def login(request): if request.method == "GET": form = LoginForm() return render(request, "login.html", {"form": form}) form = LoginForm(data=request.POST) if not form.is_valid(): return render(request, "login.html", {"form": form}) # form.cleaned_data # 手动读取字典,保存至数据库 # models.UserInfo.objects.create(name=form.cleaned_data['xx'], pwd=form.cleaned_data['yy']) return HttpResponse("成功")
- ModelForm组件
def login(request): if request.method == "GET": form = LoginForm() return render(request, "login.html", {"form": form}) form = LoginForm(data=request.POST) if not form.is_valid(): return render(request, "login.html", {"form": form}) form.save() # 自动将数据新增到数据库 return HttpResponse("成功")
注意两个问题
-
假设表单在数据库中有三个字段,但是在前端只显示了2个,提交之后,对于【sqllite】,剩下这个字段会为空
但是对于【mysql】遇到这种问题,默认不能为空,所以
form.save()
会报错# 方法一: 允许为空 null=True,blank=True # 方法二: form.instance.数据库字段名(phone) = 值 form.save()
-
在自定义ModelForm中除了数据库中的字段,还自定义了字段,但是数据库中没有,即使用户输入,后台也会忽略
class UserAddModelForm(forms.ModelForm): address = forms.CharField(label="地址") class Meta: model = models.User fields = "__all__" # 数据库和上面自定义的字段都生成标签
三.更新数据
- form组件
def login(request): if request.method == "GET": form = LoginForm(initial={"user": "武沛齐", "pwd": "123"}) return render(request, "login.html", {"form": form}) form = LoginForm(data=request.POST) if not form.is_valid(): return render(request, "login.html", {"form": form}) # form.cleaned_data # 手动读取字典,保存至数据库 models.UserInfo.objects.filter(id=1).update(name=form.cleaned_data['xx'],pwd=form.cleaned_data['y'])] return HttpResponse("成功")
- ModelForm组件
模拟场景:用户根据不同的id访问不同的页面,相当于点击了编辑按钮,然后表单中会显 示对应用户在数据库中的数据,当用户修改完成之后,点击提交按钮,将数据更新在数据库中。
def login(request): if request.method == "GET": # 这两行代码:将用户在数据库中数据在表单进行显示 user_object = model.UserInfo.object.filter(id=1).first() form = UserEditModelForm(instance=user_object) return render(request, "login.html", {"form": form}) user_object = model.UserInfo.object.filter(id=1).first() # 更新必须要有对象 instance = 某一行对象 # instance=user_object ——>告诉django该表单主要是用来更新已有的用户对象,而不是创建新的对象。 form = LoginModelForm(data=request.POST, instance=user_object) if not form.is_valid(): return render(request, "login.html", {"form": form}) # save方法更新或创建取决于上面是否有instance参数 有 ——> 更新 没 -> 创建 form.save() # 更新到数据库 return HttpResponse("成功")
· 为什么说方法的参数最好不要超过4个?
· C#.Net 筑基-优雅 LINQ 的查询艺术
· 一个自认为理想主义者的程序员,写了5年公众号、博客的初衷
· 大数据高并发核心场景实战,数据持久化之冷热分离
· 运维排查 | SaltStack 远程命令执行中文乱码问题
· 博客园众包平台:诚征3D影像景深延拓实时处理方案(预算8-15万)
· 为什么说方法的参数最好不要超过4个?
· 发布一个小功能,通过 markdown 图片语法嵌入B站视频
· 《HelloGitHub》第 111 期
· Spring AI Alibaba 1.0 正式发布!核心特性速览+老项目升级指南