mybatis-plus
1. 属性
1.1. 主键自定义生成
1: 自己实现,com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer
2: 重写 customize()时,config.setIdentifierGenerator(传自己的id生成方法);
3:自定义id生成器,需要实现com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator
点击查看代码(26位的自增uuid)
package cn.yottacloud.pc.core.mybatis;
import java.util.Random;
/**
* ULID string generator and parser class, using Crockford Base32 encoding. Only
* upper case letters are used for generation. Parsing allows upper and lower
* case letters, and i and l will be treated as 1 and o will be treated as 0.
* <br>
* <br>
* ULID generation examples:<br>
*
* <pre>
* String ulid1 = ULID.random();
* String ulid2 = ULID.random(ThreadLocalRandom.current());
* String ulid3 = ULID.random(SecureRandom.newInstance("SHA1PRNG"));
* byte[] entropy = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9 };
* String ulid4 = ULID.generate(System.currentTimeMillis(), entropy);
* </pre>
* ULID parsing examples:<br>
*
* <pre>
* String ulid = "003JZ9J6G80123456789abcdef";
* assert ULID.isValid(ulid);
* long ts = ULID.getTimestamp(ulid);
* assert ts == 123456789000L;
* byte[] entropy = ULID.getEntropy(ulid);
* </pre>
*
* @author azam
* @see <a href="http://www.crockford.com/wrmg/base32.html">Base32 Encoding</a>
* @see <a href="https://github.com/alizain/ulid">ULID</a>
* @since 0.0.1
*/
public class Ulid {
/**
* ULID string length.
*/
public static final int ULID_LENGTH = 26;
/**
* Minimum allowed timestamp value.
*/
public static final long MIN_TIME = 0x0L;
/**
* Maximum allowed timestamp value.
*/
public static final long MAX_TIME = 0x0000ffffffffffffL;
/**
* Base32 characters mapping
*/
private static final char[] C = new char[] { //
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, //
0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, //
0x47, 0x48, 0x4a, 0x4b, 0x4d, 0x4e, 0x50, 0x51, //
0x52, 0x53, 0x54, 0x56, 0x57, 0x58, 0x59, 0x5a};
/**
* {@code char} to {@code byte} O(1) mapping with alternative chars mapping
*/
private static final byte[] V = new byte[] { //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, //
(byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, //
(byte) 0x08, (byte) 0x09, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, //
(byte) 0x0d, (byte) 0x0e, (byte) 0x0f, (byte) 0x10, //
(byte) 0x11, (byte) 0xff, (byte) 0x12, (byte) 0x13, //
(byte) 0xff, (byte) 0x14, (byte) 0x15, (byte) 0xff, //
(byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, //
(byte) 0x1a, (byte) 0xff, (byte) 0x1b, (byte) 0x1c, //
(byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, //
(byte) 0x0d, (byte) 0x0e, (byte) 0x0f, (byte) 0x10, //
(byte) 0x11, (byte) 0xff, (byte) 0x12, (byte) 0x13, //
(byte) 0xff, (byte) 0x14, (byte) 0x15, (byte) 0xff, //
(byte) 0x16, (byte) 0x17, (byte) 0x18, (byte) 0x19, //
(byte) 0x1a, (byte) 0xff, (byte) 0x1b, (byte) 0x1c, //
(byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, //
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff //
};
/**
* Generate random ULID string using {@link java.util.Random} instance.
*
* @return ULID string
*/
public static String random() {
byte[] entropy = new byte[10];
Random random = new Random();
random.nextBytes(entropy);
return generate(System.currentTimeMillis(), entropy);
}
/**
* Generate random ULID string using provided {@link java.util.Random}
* instance.
*
* @param random {@link java.util.Random} instance
* @return ULID string
*/
public static String random(Random random) {
byte[] entropy = new byte[10];
random.nextBytes(entropy);
return generate(System.currentTimeMillis(), entropy);
}
/**
* Generate ULID from Unix epoch timestamp in millisecond and entropy bytes.
* Throws {@link java.lang.IllegalArgumentException} if timestamp is less
* than {@value #MIN_TIME}, is more than {@value #MAX_TIME}, or entropy
* bytes is null or less than 10 bytes.
*
* @param time Unix epoch timestamp in millisecond
* @param entropy Entropy bytes
* @return ULID string
*/
public static String generate(long time, byte[] entropy) {
if (time < MIN_TIME || time > MAX_TIME || entropy == null || entropy.length < 10) {
throw new IllegalArgumentException(
"Time is too long, or entropy is less than 10 bytes or null");
}
char[] chars = new char[26];
// time
chars[0] = C[((byte) (time >>> 45)) & 0x1f];
chars[1] = C[((byte) (time >>> 40)) & 0x1f];
chars[2] = C[((byte) (time >>> 35)) & 0x1f];
chars[3] = C[((byte) (time >>> 30)) & 0x1f];
chars[4] = C[((byte) (time >>> 25)) & 0x1f];
chars[5] = C[((byte) (time >>> 20)) & 0x1f];
chars[6] = C[((byte) (time >>> 15)) & 0x1f];
chars[7] = C[((byte) (time >>> 10)) & 0x1f];
chars[8] = C[((byte) (time >>> 5)) & 0x1f];
chars[9] = C[((byte) (time)) & 0x1f];
// entropy
chars[10] = C[(byte) ((entropy[0] & 0xff) >>> 3)];
chars[11] = C[(byte) (((entropy[0] << 2) | ((entropy[1] & 0xff) >>> 6)) & 0x1f)];
chars[12] = C[(byte) (((entropy[1] & 0xff) >>> 1) & 0x1f)];
chars[13] = C[(byte) (((entropy[1] << 4) | ((entropy[2] & 0xff) >>> 4)) & 0x1f)];
chars[14] = C[(byte) (((entropy[2] << 5) | ((entropy[3] & 0xff) >>> 7)) & 0x1f)];
chars[15] = C[(byte) (((entropy[3] & 0xff) >>> 2) & 0x1f)];
chars[16] = C[(byte) (((entropy[3] << 3) | ((entropy[4] & 0xff) >>> 5)) & 0x1f)];
chars[17] = C[(byte) (entropy[4] & 0x1f)];
chars[18] = C[(byte) ((entropy[5] & 0xff) >>> 3)];
chars[19] = C[(byte) (((entropy[5] << 2) | ((entropy[6] & 0xff) >>> 6)) & 0x1f)];
chars[20] = C[(byte) (((entropy[6] & 0xff) >>> 1) & 0x1f)];
chars[21] = C[(byte) (((entropy[6] << 4) | ((entropy[7] & 0xff) >>> 4)) & 0x1f)];
chars[22] = C[(byte) (((entropy[7] << 5) | ((entropy[8] & 0xff) >>> 7)) & 0x1f)];
chars[23] = C[(byte) (((entropy[8] & 0xff) >>> 2) & 0x1f)];
chars[24] = C[(byte) (((entropy[8] << 3) | ((entropy[9] & 0xff) >>> 5)) & 0x1f)];
chars[25] = C[(byte) (entropy[9] & 0x1f)];
return new String(chars);
}
/**
* Checks ULID string validity.
*
* @param ulid ULID string
* @return true if ULID string is valid
*/
public static boolean isValid(CharSequence ulid) {
if (ulid == null || ulid.length() != ULID_LENGTH) {
return false;
}
for (int i = 0; i < ULID_LENGTH; i++) {
// We only care for chars between 0x00 and 0xff.
char c = ulid.charAt(i);
if (c < 0 || c > V.length || V[c] == (byte) 0xff) {
return false;
}
}
return true;
}
/**
* Extract and return the timestamp part from ULID. Expects a valid ULID
* string. Call {@link io.azam.ulidj.ULID#isValid(CharSequence)} and check
* validity before calling this method if you do not trust the origin of the
* ULID string.
*
* @param ulid ULID string
* @return Unix epoch timestamp in millisecond
*/
public static long getTimestamp(CharSequence ulid) {
return (long) V[ulid.charAt(0)] << 45 //
| (long) V[ulid.charAt(1)] << 40 //
| (long) V[ulid.charAt(2)] << 35 //
| (long) V[ulid.charAt(3)] << 30 //
| (long) V[ulid.charAt(4)] << 25 //
| (long) V[ulid.charAt(5)] << 20 //
| (long) V[ulid.charAt(6)] << 15 //
| (long) V[ulid.charAt(7)] << 10 //
| (long) V[ulid.charAt(8)] << 5 //
| (long) V[ulid.charAt(9)];
}
/**
* Extract and return the entropy part from ULID. Expects a valid ULID
* string. Call {@link io.azam.ulidj.ULID#isValid(CharSequence)} and check
* validity before calling this method if you do not trust the origin of the
* ULID string.
*
* @param ulid ULID string
* @return Entropy bytes
*/
public static byte[] getEntropy(CharSequence ulid) {
byte[] bytes = new byte[10];
bytes[0] = (byte) ((V[ulid.charAt(10)] << 3) //
| (V[ulid.charAt(11)] & 0xff) >>> 2);
bytes[1] = (byte) ((V[ulid.charAt(11)] << 6) //
| V[ulid.charAt(12)] << 1 //
| (V[ulid.charAt(13)] & 0xff) >>> 4);
bytes[2] = (byte) ((V[ulid.charAt(13)] << 4) //
| (V[ulid.charAt(14)] & 0xff) >>> 1);
bytes[3] = (byte) ((V[ulid.charAt(14)] << 7) //
| V[ulid.charAt(15)] << 2 //
| (V[ulid.charAt(16)] & 0xff) >>> 3);
bytes[4] = (byte) ((V[ulid.charAt(16)] << 5) //
| V[ulid.charAt(17)]);
bytes[5] = (byte) ((V[ulid.charAt(18)] << 3) //
| (V[ulid.charAt(19)] & 0xff) >>> 2);
bytes[6] = (byte) ((V[ulid.charAt(19)] << 6) //
| V[ulid.charAt(20)] << 1 //
| (V[ulid.charAt(21)] & 0xff) >>> 4);
bytes[7] = (byte) ((V[ulid.charAt(21)] << 4) //
| (V[ulid.charAt(22)] & 0xff) >>> 1);
bytes[8] = (byte) ((V[ulid.charAt(22)] << 7) //
| V[ulid.charAt(23)] << 2 //
| (V[ulid.charAt(24)] & 0xff) >>> 3);
bytes[9] = (byte) ((V[ulid.charAt(24)] << 5) //
| V[ulid.charAt(25)]);
return bytes;
}
}
点击查看代码(id生成器)
package cn.yottacloud.pc.core.mybatis;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import java.util.concurrent.ThreadLocalRandom;
/**
* 基于ULID的全局id生成器
*/
public class UlidIdentifierGenerator implements IdentifierGenerator {
@Override
public Number nextId(Object entity) {
return -1;
}
@Override
public String nextUUID(Object entity) {
byte[] entropy = new byte[10];
ThreadLocalRandom.current().nextBytes(entropy);
# java.util.Random.Ulid
return Ulid.generate(System.currentTimeMillis(), entropy);
}
}
点击查看代码(自定义id方法)
package cn.yottacloud.pc.core.mybatis;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.MySqlDialect;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
/**
* mybatis-plus配置
*/
@Configuration
@MapperScan("cn.yottacloud.pc.**.mapper")
public class MybatisPlusConfiguration implements MybatisPlusPropertiesCustomizer {
/**
* 拦截器配置
*/
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
PaginationInnerInterceptor paginationInnerInterceptor =
new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(100L);
paginationInnerInterceptor.setDialect(new MySqlDialect());
interceptor.addInnerInterceptor(paginationInnerInterceptor);
//乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
/**
* 全局配置
*/
@Override
public void customize(MybatisPlusProperties properties) {
GlobalConfig config = properties.getGlobalConfig();
//设置逻辑删除字段和id类型
config.setDbConfig(new GlobalConfig.DbConfig()
.setIdType(IdType.ASSIGN_UUID)
);
//设置id生成器
config.setIdentifierGenerator(new UlidIdentifierGenerator());
//设置对象填充器
config.setMetaObjectHandler(new DefaultMetaObjectHandler());
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.addInterceptor(mybatisPlusInterceptor());
configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
properties.setConfiguration(configuration);
}
}
1.2. json字段自动映射List属性
java属性为对象也行!
-
1:实体类的@TableName要加属性:,autoResultMap=true
@TableName(value = "lg_kol_case_fee",autoResultMap=true) -
2:属性上加:@TableField(typeHandler = JacksonTypeHandler.class)
@TableField(typeHandler = JacksonTypeHandler.class)
private List\<String\> feeDept; -
3:mysql字段为json,则java的泛型必须为Object,必须是JacksonTypeHandler.class。字段为指定类型,需要自己实现对应泛型转换类!!
点击查看代码
package com.lg.crm.framework.mybatis.core.type;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import com.fasterxml.jackson.core.type.TypeReference;
import com.lg.crm.framework.common.util.json.JsonUtils;
import java.util.Set;
/**
* 参考 {@link com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler} 实现
* 在我们将字符串反序列化为 Set 并且泛型为 Long 时,如果每个元素的数值太小,会被处理成 Integer 类型,导致可能存在隐性的 BUG。
*
* 例如说哦,SysUserDO 的 postIds 属性
*
* @author 芋道源码
*/
public class JsonStringSetTypeHandler extends AbstractJsonTypeHandler<Object> {
private static final TypeReference<Set<String>> TYPE_REFERENCE = new TypeReference<Set<String>>(){};
@Override
protected Object parse(String json) {
return JsonUtils.parseObject(json, TYPE_REFERENCE);
}
@Override
protected String toJson(Object obj) {
return JsonUtils.toJsonString(obj);
}
}
- 4: xml的用法
<result property="feeDept" column="fee_dept" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
如果注解@TableName和@TableField加了,xml可以不加,已亲测
2. 单表api
2.1. 查
- Service最简洁写法:
前置前提:
点击查看代码
List<ScrmSupplierUsersDO> list = scrmSupplierUsersService.lambdaQuery()
.eq(ScrmSupplierUsersDO::getSupId, supId).list();
scrmSupplierUsersService.removeBatchByIds(list);
2.2. 改
- Service最简洁写法:
点击查看代码
scrmSupplierUsersService.lambdaUpdate().eq(ScrmSupplierUsersDO::getSupId, supId)
.set(ScrmSupplierUsersDO::getDeleteFlag, 1).update();
3. 手动生成雪花id
IdWorker.getIdStr()
4. update的字段为空
问题:表里有条数据,name字段原来有值,更新请求时,用户要把name改为null,直接调的方法aveOrUpdate(T e),没用UpdateWrapper去set。
原因:
plus的service方法,默认不会对属性为空的进行sql拼接!
解决办法:
1:用UpdateWrapper去set。
2:对指定字段修改策略,颗粒度(全局、某个属性【新增,编辑】,详情可看官方文档-->问答,"插入或更新的字段有 空字符串 或者 null")
// updateStrategy = FieldStrategy.IGNORED:该字段在更新时,为null或""也要参与更新
@TableField(value = "channel_id", updateStrategy = FieldStrategy.IGNORED)
private String channelId;
5. 多表分页
5.1. controller
IPage
return ViewResult.success(yqfkDangerReportHealthyService.getPageControlRecord(page, params));
5.2. service
@Override
public IPage
return yqfkDangerReportHealthyMapper.getPageControlRecord(page, params);
}
5.3. mapper
IPage
5.4. xml
点击查看代码
<select id="getPageControlRecord" resultMap="controlRecordPageResult">
SELECT
DISTINCT( person_id ),
controlResult,
dangerType,
name,
sex,
birthday,
cardNo,
mobile,
address
from
(SELECT
DISTINCT(a.person_id),
c.control_result controlResult,
( SELECT dict_label FROM sys_dict_data WHERE dict_type = 'yqfk_risk_type' AND dict_value = c.danger_type LIMIT 1 ) dangerType,
c.name name,
c.sex sex,
c.birthday birthday,
c.card_no cardNo,
c.mobile mobile,
c.address_str address,
b.check_start_time
FROM
yqfk_check_flow_process a
LEFT JOIN yqfk_danger_person_check_task b ON a.person_id = b.person_id and a.task_id=b.id
LEFT JOIN yqfk_danger_person c ON a.person_id = c.id
WHERE
a.object_type = 0
AND a.accept_object_id = #{params.userId}
AND a.del_flag = 0
AND b.del_flag = 0
AND c.del_flag =0
and c.control_result = #{params.controlStatus}
<if test="params.keyword != null and params.keyword != ''">
and
(
c.name like CONCAT('%',#{params.keyword},'%')
or
c.card_no = #{params.keyword}
or
c.mobile=#{params.keyword}
)
</if>
ORDER BY b.check_start_time desc
) a
</select>
6. 日志输出
6.1. 第1种
不打印sql查询的结果集,可控制到打印哪些包下的sql, 可打印java方法和sql。
debug级别,才会打印sql
点击查看代码
# 日志文件配置
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
level:
# 配置自己写的 MyBatis Mapper 打印日志
com.lg.scrm.module.infra.dal.mysql: info
com.lg.scrm.module.infra.dal.mysql.job.JobLogMapper: info # 配置 JobLogMapper 的日志级别为 info
com.lg.scrm.module.system.dal.mysql: info
com.lg.scrm.module.lg.dal.mysql: info
6.2. 第2种
纯sql打印,
会输出具体每一行数据,select * ,查出50条,就会打印这50条
点击查看代码
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构