@Select 业务查询对象

public @interface Select {
// 手动 SQL 部分
    String value();

// 通过实体获取 select SQL
    Class<?> entityClass() default Void.class;

// 参数
    String params() default "";

// 指定参数如果为null,返回结果直接是 null。一般设置所有 param,参数都不为 null 请求查询,否则直接返回 null;如果都不设置,参数会当作 null 处理
    String[] nullWhenParamsIsNull() default {};

// 是否级联查询
    boolean cascadeSelect() default true;
}

手动写select SQL,并映射到对象上(POJO)

@Select(value = "SELECT id, code, description FROM mm_material WHERE id = :id",
       params = "id@stockLocation.materialId", nullWhenParamsIsNull = "id", cascadeSelect = false)
MaterialVO material;

实体对象select SQL,拼接 value Condition 并映射到对象上(POJO)

@Select(entityClass = Material.class, // select
        value = "WHERE id = :id", // condition
        params = "id@stockLocation.materialId", nullWhenParamsIsNull = "id", cascadeSelect = false)
Material material;


@Select(entityClass = Classification.class,
          value = "WHERE EXISTS (select 1 from mm_material_classification WHERE classification_id = mm_classification.id AND material_id = :id)",
          params = "id@stockLocation.materialId", nullWhenParamsIsNull = "id") 
private List<Classification> classificationList;

查询对象

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@SuperBuilder
@Table
public class UserSelect extends EntityId<Long> {

    /**
     * 一对一主键关联
     * 在 service 中处理
     */
    @OneToMany(oneToOne = true, joinColumnId = "id", mappedBy = "id")
    @NotNull
    IdCard idCard;

    /**
     * 一对多外键关联
     */
    @OneToMany(mappedBy = "user", joinColumnId = "user_id")
    List<Pet> petList;

    @ManyToMany(tableName = "t_user_role", joinColumnId = "user_id", inverseJoinColumnId = "role_id"/*, cascadeSave = true, mappedBy = "userList"*/)
    List<Role> roleList;

    // @Select 查询
    private Long userId;

    @Select(
            entityClass = User.class,
            value = "WHERE id = :userId",
            params = "userId@userId", nullWhenParamsIsNull = "userId"
    )
    private User user;

    @Select(
            entityClass = User.class,
            value = "WHERE id = :userId",
            params = "userId@userId", nullWhenParamsIsNull = "userId"
    )
    private Map<String, Object> map;

    @Select(
            value = "SELECT * FROM sys_user"
    )
    private List<Map<String, Object>> userList;

    @Select(
            entityClass = User.class,
            value = "WHERE id = :userId",
            params = "userId@userId", nullWhenParamsIsNull = "userId"
    )
    private List<User> userList2;

}

测试1

    @Test
    public void testSelect1() {
        List<User> userList = entityDAO.select(Map.of("id", userId));
        User user = userList.get(0);

        User user2 = new User();
        user2.setId(userId);
        entityDAO.cascadeSelect(List.of(user2));

        List<User> list = entityDAO.select(user2);
        assertEquals(true, EqualsBuilder.reflectionEquals(list.get(0), user));

        UserSelect select = new UserSelect();

        BeanUtils.copyProperties(SerializationUtils.clone(user), select);

        select.getIdCard().setCode(null);
        for (Role role : select.getRoleList()) {
            role.setUserList(null);
            role.setName(null);
        }

        for (Pet pet : select.getPetList()) {
            pet.setUser(null);
            pet.setName(null);
        }

        entityDAOSupport.select(select);

        user2.setRoleList(select.getRoleList());
        user2.setNameList(user.getNameList());
        user2.setIdCard(select.getIdCard());
        user2.setPetList(select.getPetList());
        user2.setName(user.getName());
        user2.setBaseEntityInfo(user.getBaseEntityInfo());

        assertEquals(true, EqualsBuilder.reflectionEquals(user2, user));

        List<UserSelect> userSelectList1 = entityDAOSupport.select(UserSelect.class, "select id from t_user where id = :id", Map.of("id", userId));
        List<UserSelect> userSelectList2 = entityDAOSupport.select(UserSelect.class, "select id from t_user where id = ?", userId);
        assertEquals(true, EqualsBuilder.reflectionEquals(userSelectList1.get(0), userSelectList2.get(0)));
    }

测试2

    @Test
    public void testSelect2() {
        UserSelect userSelect = new userSelect();
        userSelect.setUserId(1L);
        UserSelect select = entityDAOSupport.select(userSelect);
        System.out.println(select);
    }

Postman 保存 token 到环境变量

思路

用 Postman 的 Tests(测试脚本) 从登录接口的响应里提取 token,存进环境变量(Environment Variable),然后在其他请求的 Headers 里引用这个变量即可,无需每次手动复制粘贴。

步骤

1. 在登录请求的 Tests 标签页写脚本

打开你的登录接口请求 → 点击 Tests(新版 Postman 叫 Post-response)→ 写入:

// 解析响应体
const res = pm.response.json();

if (res.success && res.data && res.data.token) {
    // 存入环境变量(推荐)
    pm.environment.set("token", res.data.token);
    console.log("Token 已保存:", res.data.token);
} else {
    console.warn("登录失败或未拿到 token", res);
}

如果你没有建 Environment,也可以用 pm.globals.set("token", res.data.token) 存全局变量,效果类似,但 Environment 更规范(不同环境/账号互不干扰)。

2. 确认 Environment 已选中

右上角环境下拉框,选中你正在用的那个 Environment(哪怕是空的也要建一个并选中),否则 pm.environment.set 不会生效。

3. 在其它请求里引用这个变量

在需要携带 token 的请求上:

方式 A:手动在 Headers 里加

Key Value
Authorization {{token}}

或者如果后端要求带 Bearer 前缀:

Authorization: Bearer {{token}}

具体 header 名(AuthorizationTokenX-Token 等)要和你后端拦截器实际读取的 header 名一致——从你贴的 AuthService 代码看,它内部没有体现具体是从哪个 header 读 token 的(是在 Controller/拦截器层做的),需要你确认一下实际接口用的 header 名。

方式 B:在 Authorization 标签页配置(更推荐,管理更清晰)
– 请求的 Authorization 标签页 → Type 选 API KeyBearer Token
– Key 填 token(或你后端真实的 header 名),Value 填 {{token}}
– Add to 选 Header

4. 让整个 Collection 都自动带上 token(一次配置,全局生效)

不想每个请求都单独配置的话:

  1. 点击 Collection 名称EditAuthorization 标签页
  2. Type 选 API Key
  3. Key: Authorization(或你的实际 header 名),Value: {{token}},Add to: Header
  4. 保存

之后 Collection 下所有请求,只要它们自己的 Authorization 设置成 Inherit auth from parent(这是新建请求的默认值),就会自动带上 {{token}},不需要逐个配置。

效果

  1. 先跑一次登录请求 → Tests 脚本自动把 token 存进环境变量
  2. 后续所有请求(配置了 Inherit auth from parent 或手动写了 {{token}} 的)都会自动带上最新的 token
  3. 重新登录一次,变量自动更新,无需手动复制

2026 年独立开发者开发 APP 最佳实践

Web 技术栈

TanStack Start
TypeScript
Tailwind
shadcn/ui
Base UI

better-auth

Vercel AI SDK

TanStack Query
TanStack Form

Drizzle ORM
PostgreSQL

oxlint
oxfmt

后端技术栈

Hono

Cloudflare Workers

Cloudflare D1(早期)
↓
Postgres(正式阶段)

Cloudflare R2
Cloudflare KV
Cloudflare Queues

Mobile 技术栈(产品验证后)

Expo
React Native

Expo Router

NativeWind

React Native Reusables

Reanimated
Gesture Handler

EAS

SQLParamCleaner 不定条件查询

    @Test
    public void testQuerySQLParamCleaner() {
        // sql 有条件查询
        SQLParamCleaner.FormatParam formatParam =
                SQLParamCleaner.formatSql("select * from mm_incoterm where description = :description OR code like :code ",
                        Map.of("description", "work", "code", "CI")
                );

        List<Incoterm> incotermList = baseService.getTableDAO().select(Incoterm.class, formatParam.formatSql(), formatParam.formatMap());
        print(incotermList);

        System.out.println("-------------------");

        formatParam =
                SQLParamCleaner.formatSql("description = :description AND code like :code",
                        Map.of("description", "work"),
                        true // 如果没有值,条件设置NULL,这里没有给 code 值,formatSql = description = :description AND code IS NULL
                );
        // condition 有条件查询
        incotermList = baseService.select(formatParam.formatSql(), formatParam.formatMap());
        print(incotermList);
    }

    private void print(List<Incoterm> incotermList) {
        for (Incoterm incoterm : incotermList) {
            System.out.println(incoterm.getCode() + ":" + incoterm.getDescription());
        }
    }

sharp-database 添加多数据源

手动配置后, JdbcTemplateAutoConfiguration,将不会自动注入,所有的数据源都需要自行注入管理!!!

配置类

@Configuration
public class PostgresConfig {

    @Bean
    public TableDAO postgresTableDAO(NamedParameterJdbcTemplate postgresNamedParameterJdbcTemplate) {
        return new TableDAOImpl(postgresNamedParameterJdbcTemplate);
    }

    @Bean
    public DataSource postgresDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/test");
        dataSource.setUsername("postgres");
        dataSource.setPassword("123");
        return dataSource;
    }

    @Bean
    public NamedParameterJdbcTemplate postgresNamedParameterJdbcTemplate(DataSource postgresDataSource) {
        return new NamedParameterJdbcTemplate(postgresDataSource);
    }

    @Bean
    public TableGenerator postgresTableGenerator(NamedParameterJdbcTemplate postgresNamedParameterJdbcTemplate) {
        return new PostgresSQLTableGenerator(postgresNamedParameterJdbcTemplate.getJdbcTemplate());
    }
}

PostgresEntityDAOImpl.java

@FieldDefaults(level = AccessLevel.PRIVATE)
@Validated
public class PostgresEntityDAOImpl<T, ID> extends EntityDAOImpl<T, ID> {

    @Resource
    @Qualifier("postgresTableDAO")
    @Getter
    private TableDAO tableDAO;

}

测试

@SpringBootTest
public class PostgresTableDAOTest {

    @Autowired
    private TableDAO postgresTableDAO;

    @Autowired
    private ProductDAO productDAO;

    @Autowired
    private TableGenerator postgresTableGenerator;

    @Test
    public void testTableGenerator() {
        postgresTableGenerator.createTable(Product.class);
    }

    @Test
    public void testPostgresTableDAO() {
        productDAO.insert(Product.builder().code("11").description("Product A").build());
    }
}