<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[邢栋博客]]></title> 
<atom:link href="https://xingdong365.com/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[邢栋博客，Action博客，记录工作和生活中的点点滴滴]]></description>
<link>https://xingdong365.com/</link>
<language>zh-cn</language>

<item>
    <title>Spring Boot 控制器接收参数全攻略</title>
    <link>https://xingdong365.com/program/459.html</link>
    <description><![CDATA[<h1>Spring Boot 控制器接收参数全攻略（含单参/多参示例）</h1>
<blockquote>
<p>在 Spring Boot 开发中，Controller 层接收前端参数是最基础也最容易踩坑的地方。本文用最直观的方式，把 GET/POST 请求下的<strong>单参数</strong>、<strong>多参数</strong>、<strong>混合参数</strong>、<strong>数组集合</strong>场景一网打尽，附带代码示例和避坑指南，建议收藏。</p>
</blockquote>
<hr />
<h2>一、参数接收方式概览</h2>
<table>
<thead>
<tr>
<th style="text-align: left;">参数来源</th>
<th style="text-align: left;">推荐注解</th>
<th style="text-align: left;">适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">Query String（<code>?key=value</code>）</td>
<td style="text-align: left;"><code>@RequestParam</code></td>
<td style="text-align: left;">GET 搜索、分页、筛选</td>
</tr>
<tr>
<td style="text-align: left;">URL 路径占位符（<code>/{id}</code>）</td>
<td style="text-align: left;"><code>@PathVariable</code></td>
<td style="text-align: left;">RESTful 风格详情接口</td>
</tr>
<tr>
<td style="text-align: left;">JSON 请求体（Body）</td>
<td style="text-align: left;"><code>@RequestBody</code></td>
<td style="text-align: left;">POST/PUT 新增、更新</td>
</tr>
<tr>
<td style="text-align: left;">Form 表单提交</td>
<td style="text-align: left;">无注解 / 实体类</td>
<td style="text-align: left;">传统表单场景</td>
</tr>
<tr>
<td style="text-align: left;">请求头（Header）</td>
<td style="text-align: left;"><code>@RequestHeader</code></td>
<td style="text-align: left;">Token 鉴权、传递固定参数</td>
</tr>
</tbody>
</table>
<hr />
<h2>二、GET 请求参数接收</h2>
<h3>1. 单个参数</h3>
<p><strong>方式一：路径占位符（RESTful）</strong></p>
<pre><code class="language-java">// 前端请求：GET /user/1001
@GetMapping("/user/{id}")
public String getById(@PathVariable Long id) {
    return "查询单个用户ID：" + id;
}</code></pre>
<p><strong>方式二：Query 参数（? 传参）</strong></p>
<pre><code class="language-java">// 前端请求：GET /user?id=1001
@GetMapping("/user")
public String getById2(@RequestParam Long id) {
    return "查询单个用户ID：" + id;
}</code></pre>
<hr />
<h3>2. 多个参数</h3>
<p><strong>写法一：多个 @RequestParam 逐一列出</strong></p>
<pre><code class="language-java">// 前端请求：GET /user?name=Jack&amp;age=25&amp;city=北京
@GetMapping("/user")
public String getUsers(@RequestParam String name, 
                       @RequestParam Integer age, 
                       @RequestParam String city) {
    return "姓名：" + name + "，年龄：" + age + "，城市：" + city;
}</code></pre>
<p><strong>写法二：使用 Map 接收（参数不固定时）</strong></p>
<pre><code class="language-java">// 前端请求：GET /user?name=Jack&amp;age=25&amp;hobby=football
@GetMapping("/user/map")
public String getUsersByMap(@RequestParam Map&lt;String, String&gt; params) {
    return "所有参数：" + params.toString();
    // 输出：{name=Jack, age=25, hobby=football}
}</code></pre>
<p><strong>写法三：使用实体类接收（最优雅，强烈推荐）</strong></p>
<pre><code class="language-java">// 前端请求：GET /user?name=Jack&amp;age=25&amp;city=北京
@GetMapping("/user/entity")
public String getUsersByEntity(UserQuery query) {
    return "姓名：" + query.getName() + "，年龄：" + query.getAge() + "，城市：" + query.getCity();
}

// 对应的实体类（需有 getter/setter）
public class UserQuery {
    private String name;
    private Integer age;
    private String city;
    // 省略 getter/setter...
}</code></pre>
<hr />
<h2>三、POST 请求参数接收</h2>
<h3>1. 单个参数（JSON）</h3>
<pre><code class="language-java">// 前端请求：POST /user
// Body: {"name": "Jack"}
@PostMapping("/user")
public String createUser(@RequestBody User user) {
    return "创建用户：" + user.getName();
}</code></pre>
<hr />
<h3>2. 多个参数（JSON 对象）</h3>
<pre><code class="language-java">// 前端请求：POST /user
// Body: {"name":"Jack", "age":25, "email":"jack@qq.com", "address":"北京"}
@PostMapping("/user")
public String createUserFull(@RequestBody User user) {
    return "姓名：" + user.getName() + 
           "，年龄：" + user.getAge() + 
           "，邮箱：" + user.getEmail() + 
           "，地址：" + user.getAddress();
}

// 对应的实体类
public class User {
    private String name;
    private Integer age;
    private String email;
    private String address;
    // 省略 getter/setter...
}</code></pre>
<h3>3. 不想建实体类？用 Map 接收 JSON</h3>
<pre><code class="language-java">// 前端请求：POST /user/map
// Body: {"name":"Jack", "age":25, "score":98.5}
@PostMapping("/user/map")
public String createUserByMap(@RequestBody Map&lt;String, Object&gt; params) {
    return "所有参数：" + params.toString();
    // 输出：{name=Jack, age=25, score=98.5}
}</code></pre>
<hr />
<h2>四、混合场景：路径 + Query + Body 同时使用</h2>
<p>实际开发中，一个接口可能同时包含多种参数来源，这是完全合法的：</p>
<pre><code class="language-java">// 前端请求：PUT /order/1001?status=PAID
// Body: {"productName": "手机", "price": 5999}
@PutMapping("/order/{orderId}")
public String updateOrder(@PathVariable Long orderId,          // 路径参数
                          @RequestParam String status,         // Query 参数
                          @RequestBody Order order) {          // Body 参数
    return "订单ID：" + orderId + 
           "，状态：" + status + 
           "，商品：" + order.getProductName() + 
           "，价格：" + order.getPrice();
}</code></pre>
<hr />
<h2>五、数组 / 集合参数接收</h2>
<h3>GET 请求：多个相同参数名</h3>
<pre><code class="language-java">// 前端请求：GET /users?ids=1001&amp;ids=1002&amp;ids=1003
@GetMapping("/users")
public String getUsers(@RequestParam List&lt;Long&gt; ids) {
    return "用户ID列表：" + ids.toString(); // 输出：[1001, 1002, 1003]
}</code></pre>
<h3>POST 请求：JSON 数组</h3>
<pre><code class="language-java">// 前端请求：POST /users/batch
// Body: [1001, 1002, 1003]
@PostMapping("/users/batch")
public String batchGetUsers(@RequestBody List&lt;Long&gt; ids) {
    return "批量查询ID：" + ids.toString();
}</code></pre>
<hr />
<h2>六、速查对照表（单参 vs 多参）</h2>
<table>
<thead>
<tr>
<th style="text-align: left;">场景</th>
<th style="text-align: left;">单个参数示例</th>
<th style="text-align: left;">多个参数示例</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">GET - 路径占位符</td>
<td style="text-align: left;"><code>@PathVariable Long id</code></td>
<td style="text-align: left;">多个占位符：<code>/{id}/{name}</code> 对应多个 <code>@PathVariable</code></td>
</tr>
<tr>
<td style="text-align: left;">GET - Query 参数</td>
<td style="text-align: left;"><code>@RequestParam Long id</code></td>
<td style="text-align: left;"><code>@RequestParam String name, Integer age</code> 或实体类</td>
</tr>
<tr>
<td style="text-align: left;">POST - JSON Body</td>
<td style="text-align: left;"><code>@RequestBody User user</code>（实体类只有一个字段）</td>
<td style="text-align: left;"><code>@RequestBody User user</code>（实体类有多个字段）或 <code>Map</code></td>
</tr>
<tr>
<td style="text-align: left;">参数不固定</td>
<td style="text-align: left;">建议用 <code>Map</code></td>
<td style="text-align: left;"><code>@RequestParam Map</code> 或 <code>@RequestBody Map</code></td>
</tr>
</tbody>
</table>
<hr />
<h2>七、避坑指南（重点！）</h2>
<table>
<thead>
<tr>
<th style="text-align: left;">常见错误</th>
<th style="text-align: left;">正确做法</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">POST 传 JSON 却用 <code>@RequestParam</code> 接收</td>
<td style="text-align: left;">改为 <code>@RequestBody</code></td>
</tr>
<tr>
<td style="text-align: left;">GET 传 Query 参数却用 <code>@RequestBody</code></td>
<td style="text-align: left;">GET 没有 Body，改为 <code>@RequestParam</code> 或实体类无注解</td>
</tr>
<tr>
<td style="text-align: left;">参数名与前端字段名不一致导致绑定失败</td>
<td style="text-align: left;">使用 <code>@RequestParam("前端字段名")</code> 或 <code>@JsonProperty</code></td>
</tr>
<tr>
<td style="text-align: left;">前端少传参数导致 400 错误</td>
<td style="text-align: left;">加 <code>required = false</code> 或设置 <code>defaultValue</code></td>
</tr>
<tr>
<td style="text-align: left;">时间格式传参报错</td>
<td style="text-align: left;">实体类字段加 <code>@DateTimeFormat(pattern = "yyyy-MM-dd")</code></td>
</tr>
<tr>
<td style="text-align: left;">POST 表单提交（<code>application/x-www-form-urlencoded</code>）误用 <code>@RequestBody</code></td>
<td style="text-align: left;">直接用实体类接收，不加任何注解</td>
</tr>
</tbody>
</table>
<hr />
<h2>八、开发实践建议</h2>
<ol>
<li><strong>统一规范</strong>：项目内约定好 GET 用 Query + 实体类，POST/PUT 用 JSON + <code>@RequestBody</code>，避免风格混乱。</li>
<li><strong>优先使用实体类</strong>：参数超过 3 个时，建议封装为实体类，提高代码可读性和可维护性。</li>
<li><strong>参数校验</strong>：配合 <code>@Valid</code> + <code>@NotNull</code> 等注解做参数校验，减少业务层判空代码。</li>
<li><strong>日志打印</strong>：在 Controller 层打印接收到的参数，方便联调排查问题。</li>
</ol>
<hr />
<h2>总结</h2>
<p>Spring Boot 接收参数的方式虽然多样，但总结下来无非是<strong>参数来源</strong>和<strong>注解选择</strong>的排列组合。记住三个核心注解：</p>
<ul>
<li><code>@PathVariable</code> → URL 路径中的值</li>
<li><code>@RequestParam</code> → URL 问号后面的值</li>
<li><code>@RequestBody</code> → 请求体中的 JSON</li>
</ul>
<p>掌握这三个，再加上实体类自动绑定，就能覆盖 95% 以上的开发场景。</p>
<p>如果你在实践中有任何疑问，欢迎在评论区留言交流！</p>
<hr />
<hr />]]></description>
    <pubDate>Mon, 17 Aug 2026 15:48:32 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/program/459.html</guid>
</item>
<item>
    <title>银河麒麟服务器版使用docker安装mysql5.7</title>
    <link>https://xingdong365.com/network/458.html</link>
    <description><![CDATA[<h1>需求</h1>
<p>客户要求在银河麒麟服务器版使用docker安装mysql5.7</p>
<h1>难点</h1>
<p>1、mysql版本 ARM 架构的银河麒麟架构匹配，不然不会报错<br />
2、需要的是 arm64v8/mysql:5.7<br />
3、在docker镜像官网找到的最低版本都是8.0了 </p>
<h1>解决</h1>
<p>最终在 腾讯云的公有镜像里找到了（<a href="https://console.cloud.tencent.com/tcr/publicimage/tag?rid=1&amp;reponame=arm64v8%2Fmysql">https://console.cloud.tencent.com/tcr/publicimage/tag?rid=1&amp;reponame=arm64v8%2Fmysql</a>）<br />
docker pull  ccr.ccs.tencentyun.com/arm64v8/mysql:5.7.31</p>]]></description>
    <pubDate>Fri, 27 Mar 2026 09:15:47 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/network/458.html</guid>
</item>
<item>
    <title>Java Stream API 收集器</title>
    <link>https://xingdong365.com/program/457.html</link>
    <description><![CDATA[<h1>Java 收集器工厂方法使用案例</h1>
<p>以下是 <code>Collectors</code> 类中常用工厂方法的详细使用案例：</p>
<h2>1. <code>toList()</code> - 收集到列表</h2>
<pre><code class="language-java">import java.util.*;
import java.util.stream.*;

public class CollectorExamples {
    public static void main(String[] args) {
        // 示例数据
        List&lt;String&gt; names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Alice");

        // toList() - 收集到列表
        List&lt;String&gt; nameList = names.stream()
            .filter(name -&gt; name.length() &gt; 3)
            .collect(Collectors.toList());
        System.out.println("toList 示例: " + nameList);
        // 输出: [Alice, Charlie, David, Alice] (保留重复元素)

        // 处理数值类型
        List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5);
        List&lt;Integer&gt; squares = numbers.stream()
            .map(n -&gt; n * n)
            .collect(Collectors.toList());
        System.out.println("数值平方列表: " + squares);
        // 输出: [1, 4, 9, 16, 25]
    }
}</code></pre>
<h2>2. <code>toSet()</code> - 收集到集合（去重）</h2>
<pre><code class="language-java">import java.util.*;
import java.util.stream.*;

public class CollectorExamples {
    public static void main(String[] args) {
        List&lt;String&gt; names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Alice");

        // toSet() - 自动去重
        Set&lt;String&gt; nameSet = names.stream()
            .filter(name -&gt; name.length() &gt; 3)
            .collect(Collectors.toSet());
        System.out.println("toSet 示例: " + nameSet);
        // 输出: [Alice, Charlie, David] (自动去重，顺序可能不同)

        // 自定义对象示例
        List&lt;Employee&gt; employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Alice", "IT", 5500),
            new Employee("Charlie", "HR", 5200)
        );

        // 根据姓名去重收集
        Set&lt;String&gt; uniqueNames = employees.stream()
            .map(Employee::getName)
            .collect(Collectors.toSet());
        System.out.println("员工姓名集合: " + uniqueNames);
        // 输出: [Alice, Bob, Charlie]
    }

    static class Employee {
        private String name;
        private String department;
        private double salary;

        public Employee(String name, String department, double salary) {
            this.name = name;
            this.department = department;
            this.salary = salary;
        }

        public String getName() { return name; }
        public String getDepartment() { return department; }
        public double getSalary() { return salary; }
    }
}</code></pre>
<h2>3. <code>toMap()</code> - 收集到映射</h2>
<pre><code class="language-java">import java.util.*;
import java.util.stream.*;

public class CollectorExamples {
    public static void main(String[] args) {
        List&lt;Employee&gt; employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Charlie", "HR", 5200),
            new Employee("David", "IT", 6500)
        );

        // 简单的 toMap - 姓名作为键，员工对象作为值
        Map&lt;String, Employee&gt; employeeMap = employees.stream()
            .collect(Collectors.toMap(
                Employee::getName,  // 键提取器
                employee -&gt; employee  // 值提取器
            ));
        System.out.println("员工映射: " + 
            employeeMap.keySet().stream()
                .map(key -&gt; key + "=" + employeeMap.get(key).getDepartment())
                .collect(Collectors.joining(", ")));
        // 输出类似: Alice=HR, Bob=IT, Charlie=HR, David=IT

        // 处理键冲突的情况
        List&lt;Employee&gt; employeesWithDuplicates = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Alice", "Finance", 5500)  // 重复的键
        );

        Map&lt;String, Employee&gt; mergedMap = employeesWithDuplicates.stream()
            .collect(Collectors.toMap(
                Employee::getName,
                employee -&gt; employee,
                (existing, replacement) -&gt; {
                    // 处理键冲突：保留工资更高的员工
                    return existing.getSalary() &gt;= replacement.getSalary() 
                        ? existing : replacement;
                }
            ));
        System.out.println("处理冲突后的映射: " + 
            mergedMap.entrySet().stream()
                .map(e -&gt; e.getKey() + "=" + e.getValue().getDepartment())
                .collect(Collectors.joining(", ")));
        // 输出: Alice=Finance, Bob=IT

        // 指定特定的 Map 实现
        Map&lt;String, Double&gt; salaryMap = employees.stream()
            .collect(Collectors.toMap(
                Employee::getName,
                Employee::getSalary,
                (v1, v2) -&gt; v1,  // 如果键重复，保留第一个值
                TreeMap::new  // 使用 TreeMap 保持排序
            ));
        System.out.println("排序的薪资映射: " + salaryMap);
        // 输出: {Alice=5000.0, Bob=6000.0, Charlie=5200.0, David=6500.0}
    }
}</code></pre>
<h2>4. <code>groupingBy()</code> - 分组收集</h2>
<pre><code class="language-java">import java.util.*;
import java.util.stream.*;

public class CollectorExamples {
    public static void main(String[] args) {
        List&lt;Employee&gt; employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Charlie", "HR", 5200),
            new Employee("David", "IT", 6500),
            new Employee("Eve", "Finance", 7000),
            new Employee("Frank", "Finance", 5500)
        );

        // 简单分组 - 按部门分组
        Map&lt;String, List&lt;Employee&gt;&gt; employeesByDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment));

        System.out.println("按部门分组:");
        employeesByDept.forEach((dept, empList) -&gt; 
            System.out.println(dept + ": " + 
                empList.stream()
                    .map(Employee::getName)
                    .collect(Collectors.joining(", ")))
        );
        // 输出:
        // HR: Alice, Charlie
        // Finance: Eve, Frank
        // IT: Bob, David

        // 分组后对值进行进一步处理
        Map&lt;String, Long&gt; deptCount = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.counting()  // 统计每个部门的人数
            ));
        System.out.println("各部门人数: " + deptCount);
        // 输出: {HR=2, Finance=2, IT=2}

        // 分组后计算平均薪资
        Map&lt;String, Double&gt; avgSalaryByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.averagingDouble(Employee::getSalary)
            ));
        System.out.println("各部门平均薪资: " + avgSalaryByDept);
        // 输出类似: {HR=5100.0, Finance=6250.0, IT=6250.0}

        // 多级分组 - 先按部门，再按薪资级别
        Map&lt;String, Map&lt;String, List&lt;Employee&gt;&gt;&gt; multiLevelGrouping = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.groupingBy(emp -&gt; 
                    emp.getSalary() &gt;= 6000 ? "高薪" : "普通"
                )
            ));

        System.out.println("\n多级分组结果:");
        multiLevelGrouping.forEach((dept, salaryGroup) -&gt; {
            System.out.println("部门: " + dept);
            salaryGroup.forEach((level, empList) -&gt;
                System.out.println("  " + level + ": " +
                    empList.stream()
                        .map(Employee::getName)
                        .collect(Collectors.joining(", ")))
            );
        });
        // 输出:
        // 部门: HR
        //   普通: Alice, Charlie
        // 部门: Finance
        //   高薪: Eve
        //   普通: Frank
        // 部门: IT
        //   高薪: Bob, David

        // 使用特定 Map 实现的分组
        Map&lt;String, Set&lt;String&gt;&gt; namesByDept = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                TreeMap::new,  // 使用 TreeMap 保持部门名称排序
                Collectors.mapping(
                    Employee::getName,
                    Collectors.toSet()  // 收集为 Set 去重
                )
            ));
        System.out.println("\n排序的部门-姓名映射: " + namesByDept);
        // 输出类似: {Finance=[Eve, Frank], HR=[Alice, Charlie], IT=[Bob, David]}
    }
}</code></pre>
<h2>5. <code>joining()</code> - 连接字符串</h2>
<pre><code class="language-java">import java.util.*;
import java.util.stream.*;

public class CollectorExamples {
    public static void main(String[] args) {
        List&lt;String&gt; fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");

        // 简单连接
        String simpleJoin = fruits.stream()
            .collect(Collectors.joining());
        System.out.println("简单连接: " + simpleJoin);
        // 输出: AppleBananaCherryDate

        // 使用分隔符连接
        String withDelimiter = fruits.stream()
            .collect(Collectors.joining(", "));
        System.out.println("带分隔符连接: " + withDelimiter);
        // 输出: Apple, Banana, Cherry, Date

        // 使用分隔符、前缀和后缀
        String withPrefixSuffix = fruits.stream()
            .collect(Collectors.joining(", ", "[", "]"));
        System.out.println("带前缀后缀连接: " + withPrefixSuffix);
        // 输出: [Apple, Banana, Cherry, Date]

        // 结合其他操作
        String filteredJoin = fruits.stream()
            .filter(fruit -&gt; fruit.length() &gt; 5)
            .map(String::toUpperCase)
            .collect(Collectors.joining(" | ", "Fruits: ", " (end)"));
        System.out.println("过滤转换后连接: " + filteredJoin);
        // 输出: Fruits: BANANA | CHERRY (end)

        // 处理对象列表
        List&lt;Employee&gt; employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Charlie", "HR", 5200)
        );

        String employeeNames = employees.stream()
            .map(Employee::getName)
            .collect(Collectors.joining(", ", "Employees: ", ""));
        System.out.println("员工姓名连接: " + employeeNames);
        // 输出: Employees: Alice, Bob, Charlie

        // 复杂连接示例
        String complexJoin = employees.stream()
            .map(emp -&gt; emp.getName() + "(" + emp.getDepartment() + ")")
            .collect(Collectors.joining("; ", "[", "]"));
        System.out.println("复杂格式连接: " + complexJoin);
        // 输出: [Alice(HR); Bob(IT); Charlie(HR)]
    }
}</code></pre>
<h2>6. 综合示例</h2>
<pre><code class="language-java">import java.util.*;
import java.util.stream.*;

public class CollectorExamples {
    public static void main(String[] args) {
        List&lt;Employee&gt; employees = Arrays.asList(
            new Employee("Alice", "HR", 5000),
            new Employee("Bob", "IT", 6000),
            new Employee("Charlie", "HR", 5200),
            new Employee("David", "IT", 6500),
            new Employee("Eve", "Finance", 7000),
            new Employee("Frank", "Finance", 5500),
            new Employee("Grace", "IT", 6200)
        );

        // 综合应用：分析员工数据
        Map&lt;String, String&gt; departmentSummary = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    list -&gt; {
                        long count = list.size();
                        double avgSalary = list.stream()
                            .mapToDouble(Employee::getSalary)
                            .average()
                            .orElse(0.0);
                        String names = list.stream()
                            .map(Employee::getName)
                            .sorted()
                            .collect(Collectors.joining(", "));
                        return String.format("人数: %d, 平均薪资: %.2f, 员工: [%s]", 
                            count, avgSalary, names);
                    }
                )
            ));

        System.out.println("部门综合分析:");
        departmentSummary.forEach((dept, summary) -&gt; 
            System.out.println(dept + " -&gt; " + summary)
        );

        // 输出示例:
        // HR -&gt; 人数: 2, 平均薪资: 5100.00, 员工: [Alice, Charlie]
        // Finance -&gt; 人数: 2, 平均薪资: 6250.00, 员工: [Eve, Frank]
        // IT -&gt; 人数: 3, 平均薪资: 6233.33, 员工: [Bob, David, Grace]
    }
}</code></pre>
<h2>7. 其他常用收集器</h2>
<pre><code class="language-java">import java.util.*;
import java.util.stream.*;
import java.util.IntSummaryStatistics;

public class CollectorExamples {
    public static void main(String[] args) {
        List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 统计信息
        IntSummaryStatistics stats = numbers.stream()
            .collect(Collectors.summarizingInt(Integer::intValue));
        System.out.println("统计信息: " + stats);
        System.out.println("平均值: " + stats.getAverage());
        System.out.println("总数: " + stats.getSum());
        System.out.println("最大值: " + stats.getMax());
        System.out.println("最小值: " + stats.getMin());
        System.out.println("数量: " + stats.getCount());

        // 分区（分为 true 和 false 两组）
        Map&lt;Boolean, List&lt;Integer&gt;&gt; partitioned = numbers.stream()
            .collect(Collectors.partitioningBy(n -&gt; n % 2 == 0));
        System.out.println("奇偶数分区: " + partitioned);
        // 输出: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}

        // 归约操作
        Optional&lt;Integer&gt; sum = numbers.stream()
            .collect(Collectors.reducing((a, b) -&gt; a + b));
        System.out.println("总和: " + sum.orElse(0));

        // 映射后再收集
        List&lt;String&gt; numberStrings = numbers.stream()
            .collect(Collectors.mapping(
                n -&gt; "Number: " + n,
                Collectors.toList()
            ));
        System.out.println("映射后收集: " + numberStrings);
    }
}</code></pre>
<h2>总结</h2>
<table>
<thead>
<tr>
<th>收集器</th>
<th>用途</th>
<th>特点</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>toList()</code></td>
<td>收集到列表</td>
<td>保留顺序，允许重复</td>
</tr>
<tr>
<td><code>toSet()</code></td>
<td>收集到集合</td>
<td>自动去重，不保证顺序</td>
</tr>
<tr>
<td><code>toMap()</code></td>
<td>收集到映射</td>
<td>需要键值提取器，可处理键冲突</td>
</tr>
<tr>
<td><code>groupingBy()</code></td>
<td>分组收集</td>
<td>强大的分组功能，支持多级分组</td>
</tr>
<tr>
<td><code>joining()</code></td>
<td>连接字符串</td>
<td>灵活配置分隔符、前缀、后缀</td>
</tr>
</tbody>
</table>
<p>这些收集器可以组合使用，创建复杂的数据处理管道，是Java Stream API中非常强大的工具。</p>]]></description>
    <pubDate>Mon, 19 Jan 2026 13:56:16 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/program/457.html</guid>
</item>
<item>
    <title>向量数据库(used)</title>
    <link>https://xingdong365.com/database/456.html</link>
    <description><![CDATA[<h2>列表</h2>
<ol>
<li>In-memory</li>
<li>Redis-stack</li>
<li>Qdrant</li>
<li>Pinecone</li>
</ol>
<h2>对比总结</h2>
<p><a href="https://xingdong365.com/content/uploadfile/202512/99221765156824.png"><img src="https://xingdong365.com/content/uploadfile/202512/thum-99221765156824.png" alt="" /></a></p>
<h2>选择建议</h2>
<ol>
<li>快速验证/POC：In-memory</li>
<li>已有Redis基础设施：Redis-stack</li>
<li>自托管，注重性价比：Qdrant</li>
<li>无运维团队，追求快速上线：Pinecone</li>
<li>数据敏感，需私有化部署：Qdrant</li>
<li>混合数据类型需求：Redis-stack</li>
</ol>]]></description>
    <pubDate>Mon, 08 Dec 2025 09:15:29 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/database/456.html</guid>
</item>
<item>
    <title>java.lang.RuntimeException: ai.djl.engine.EngineException: Failed to load Huggingface native library.</title>
    <link>https://xingdong365.com/program/455.html</link>
    <description><![CDATA[<pre><code class="language-java">    /**
     * 加载文档并存入向量数据库
     */
    @Test
    public void testReadDocumentAndStore() {
        //使用FileSystemDocumentLoader读取指定目录下的知识库文档
        //并使用默认的文档解析器对文档进行解析(TextDocumentParser)
        Document document = FileSystemDocumentLoader.loadDocument(filePath+"/人工智能.md");
        //为了简单起见，我们暂时使用基于内存的向量存储
        InMemoryEmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
        //ingest
        //1、分割文档：默认使用递归分割器，将文档分割为多个文本片段，每个片段包含不超过 300个token，并且有 30个token的重叠部分保证连贯性
        //DocumentByParagraphSplitter(DocumentByLineSplitter(DocumentBySentenceSplitter(DocumentByWordSplitter)))
        //2、文本向量化：使用一个LangChain4j内置的轻量化向量模型对每个文本片段进行向量化
        //3、将原始文本和向量存储到向量数据库中(InMemoryEmbeddingStore)
        EmbeddingStoreIngestor.ingest(document, embeddingStore);
        //查看向量数据库内容
        System.out.println(embeddingStore);
    }</code></pre>
<p>测试此代码报错，分析为：<br />
不兼容的CPU架构<br />
错误信息显示&quot;Unexpected flavor: cpu&quot;，这通常意味着当前平台的CPU架构不被支持<br />
检查是否在Apple Silicon (M1/M2) Mac上运行，这可能需要特定的ARM64版本</p>
<p>由于我的mac是interl芯片，解决兼容问题：<br />
加入下面这个依赖</p>
<pre><code class="language-java">&lt;dependency&gt;
    &lt;groupId&gt;ai.djl.huggingface&lt;/groupId&gt;
    &lt;artifactId&gt;tokenizers&lt;/artifactId&gt;
    &lt;version&gt;0.28.0&lt;/version&gt;
&lt;/dependency&gt;</code></pre>]]></description>
    <pubDate>Fri, 05 Dec 2025 10:26:35 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/program/455.html</guid>
</item>
<item>
    <title>java aop加载流程</title>
    <link>https://xingdong365.com/program/454.html</link>
    <description><![CDATA[<h2>代码示例</h2>
<pre><code class="language-java">@Aspect 
@Component 
public class PayAspect { 
    @Before("execution(public void com.flyaction.demo.aopreview.PayServiceImpl.pay(..))") 
    public void beforeNotify() { System.out.println("-----@Before前置通知"); } 

    @After("execution(public void com.flyaction.demo.aopreview.PayServiceImpl.pay(..))") 
    public void afterNotify() { System.out.println("-----@After后置通知"); } 

    @AfterReturning("execution(public void com.flyaction.demo.aopreview.PayServiceImpl.pay(..))") 
    public void afterReturningNotify() { System.out.println("-----@AfterReturning返回通知"); } 

    @AfterThrowing("execution(public void com.flyaction.demo.aopreview.PayServiceImpl.pay(..))") 
    public void afterThrowingNotify() { System.out.println("-----@AfterThrowing异常通知"); } 

    @Around("execution(public void com.flyaction.demo.aopreview.PayServiceImpl.pay(..))") 
    public Object aroundNotify(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 
        Object retValue = null; 
        System.out.println("-----@Around环绕通知AAA"); 
        retValue = proceedingJoinPoint.proceed();//放行 
        System.out.println("-----@Around环绕通知BBB"); return retValue; 
    } 
}</code></pre>
<h2>无异常情况下</h2>
<p><a href="https://xingdong365.com/content/uploadfile/202506/c2021748943553.png"><img src="https://xingdong365.com/content/uploadfile/202506/c2021748943553.png" alt="" /></a><br />
<a href="https://xingdong365.com/content/uploadfile/202506/f8ac1748943562.png"><img src="https://xingdong365.com/content/uploadfile/202506/thum-f8ac1748943562.png" alt="" /></a></p>
<h2>异常情况下</h2>
<p><a href="https://xingdong365.com/content/uploadfile/202506/4ecf1748943569.png"><img src="https://xingdong365.com/content/uploadfile/202506/thum-4ecf1748943569.png" alt="" /></a><br />
<a href="https://xingdong365.com/content/uploadfile/202506/32371748943572.png"><img src="https://xingdong365.com/content/uploadfile/202506/thum-32371748943572.png" alt="" /></a></p>]]></description>
    <pubDate>Tue, 03 Jun 2025 17:38:08 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/program/454.html</guid>
</item>
<item>
    <title>SpringBoot配置文件示例</title>
    <link>https://xingdong365.com/program/453.html</link>
    <description><![CDATA[<h1>示例代码</h1>
<pre><code class="language-java">@Component
@ConfigurationProperties(prefix = "person") //和配置文件person前缀的所有配置进行绑定
@Data //自动生成JavaBean属性的getter/setter
//@NoArgsConstructor //自动生成无参构造器
//@AllArgsConstructor //自动生成全参构造器
public class Person {
    private String name;
    private Integer age;
    private Date birthDay;
    private Boolean like;
    private Child child; //嵌套对象
    private List&lt;Dog&gt; dogs; //数组（里面是对象）
    private Map&lt;String,Cat&gt; cats; //表示Map
}

@Data
public class Dog {
    private String name;
    private Integer age;
}

@Data
public class Child {
    private String name;
    private Integer age;
    private Date birthDay;
    private List&lt;String&gt; text; //数组
}

@Data
public class Cat {
    private String name;
    private Integer age;
}</code></pre>
<h1>properties表示法</h1>
<pre><code class="language-java">person.name=张三
person.age=18
person.birthDay=2010/10/12 12:12:12
person.like=true
person.child.name=李四
person.child.age=12
person.child.birthDay=2018/10/12
person.child.text[0]=abc
person.child.text[1]=def
person.dogs[0].name=小黑
person.dogs[0].age=3
person.dogs[1].name=小白
person.dogs[1].age=2
person.cats.c1.name=小蓝
person.cats.c1.age=3
person.cats.c2.name=小灰
person.cats.c2.age=2</code></pre>
<h1>yaml表示法</h1>
<pre><code class="language-java">person:
  name: 张三
  age: 18
  birthDay: 2010/10/10 12:12:12
  like: true
  child:
    name: 李四
    age: 20
    birthDay: 2018/10/10
    text: ["abc","def"]
  dogs:
    - name: 小黑
      age: 3
    - name: 小白
      age: 2
  cats:
    c1:
      name: 小蓝
      age: 3
    c2: {name: 小绿,age: 2} #对象也可用{}表示</code></pre>
<h1>细节</h1>
<p>● birthDay 推荐写为 birth-day<br />
● 文本：<br />
○ 单引号不会转义【\n 则为普通字符串显示】<br />
○ 双引号会转义【\n会显示为换行符】<br />
● 大文本<br />
○ |开头，大文本写在下层，保留文本格式，换行符正确显示<br />
○ &gt;开头，大文本写在下层，折叠换行符<br />
● 多文档合并<br />
○ 使用---可以把多个yaml文档合并在一个文档中，每个文档区依然认为内容独立</p>]]></description>
    <pubDate>Tue, 22 Apr 2025 14:28:27 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/program/453.html</guid>
</item>
<item>
    <title>linux下ssh-copy-id命令详解</title>
    <link>https://xingdong365.com/network/452.html</link>
    <description><![CDATA[<h1><code>ssh-copy-id</code> 命令笔记</h1>
<h2>1. 命令简介</h2>
<p><code>ssh-copy-id</code> 是一个用于将本地 SSH 公钥自动添加到远程服务器 <code>authorized_keys</code> 文件的工具，实现免密 SSH 登录。</p>
<h2>2. 基本语法</h2>
<pre><code class="language-bash">ssh-copy-id [-i [identity_file]] [user@]hostname</code></pre>
<h2>3. 常用选项</h2>
<table>
<thead>
<tr>
<th>选项</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>-i [identity_file]</code></td>
<td>指定公钥文件（默认：<code>~/.ssh/id_rsa.pub</code>）</td>
</tr>
<tr>
<td><code>-p port</code></td>
<td>指定 SSH 端口（非默认 22 端口时使用）</td>
</tr>
<tr>
<td><code>-f</code></td>
<td>强制模式，不检查密钥是否已存在</td>
</tr>
<tr>
<td><code>-n</code></td>
<td>只打印命令而不执行</td>
</tr>
<tr>
<td><code>-o ssh_option</code></td>
<td>传递其他 SSH 选项</td>
</tr>
</tbody>
</table>
<h2>4. 使用示例</h2>
<h3>4.1 基本使用</h3>
<pre><code class="language-bash">ssh-copy-id user@192.168.1.100</code></pre>
<h3>4.2 指定公钥文件</h3>
<pre><code class="language-bash">ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host</code></pre>
<h3>4.3 指定非标准端口</h3>
<pre><code class="language-bash">ssh-copy-id -p 2222 user@host</code></pre>
<h3>4.4 组合使用</h3>
<pre><code class="language-bash">ssh-copy-id -i ~/.ssh/work_key.pub -p 2222 user@work-server</code></pre>
<h2>5. 工作原理</h2>
<ol>
<li>检查本地公钥文件</li>
<li>通过 SSH 连接到目标服务器</li>
<li>将公钥追加到 <code>~/.ssh/authorized_keys</code></li>
<li>设置正确的文件权限：
<ul>
<li><code>~/.ssh</code> → 700</li>
<li><code>~/.ssh/authorized_keys</code> → 600</li>
</ul></li>
</ol>
<h2>6. 手动替代方案</h2>
<p>当 <code>ssh-copy-id</code> 不可用时：</p>
<pre><code class="language-bash">cat ~/.ssh/id_rsa.pub | ssh user@host "mkdir -p ~/.ssh &amp;&amp; 
cat &gt;&gt; ~/.ssh/authorized_keys &amp;&amp; 
chmod 700 ~/.ssh &amp;&amp; 
chmod 600 ~/.ssh/authorized_keys"</code></pre>
<h2>7. 验证配置</h2>
<pre><code class="language-bash">ssh user@host</code></pre>
<p>成功则无需输入密码直接登录</p>
<h2>8. 注意事项</h2>
<ul>
<li>确保已生成密钥对：<code>ssh-keygen</code></li>
<li>首次连接需确认主机指纹</li>
<li>检查远程服务器文件权限：
<pre><code class="language-bash">chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys</code></pre></li>
<li>某些系统需在 <code>/etc/ssh/sshd_config</code> 中启用：
<pre><code>PubkeyAuthentication yes</code></pre></li>
</ul>
<h2>9. 故障排查</h2>
<ul>
<li><strong>权限问题</strong>：
<pre><code class="language-bash">ssh -v user@host  # 查看详细错误</code></pre></li>
<li><strong>服务端配置</strong>：<br />
检查 <code>/var/log/secure</code> 或 <code>/var/log/auth.log</code></li>
</ul>
<h2>10. 扩展知识</h2>
<ul>
<li>生成新密钥：<code>ssh-keygen -t ed25519</code></li>
<li>查看公钥：<code>cat ~/.ssh/id_ed25519.pub</code></li>
<li>禁用密码登录（安全建议）：
<pre><code class="language-bash"># 在 /etc/ssh/sshd_config 中设置：
PasswordAuthentication no</code></pre>
<pre><code></code></pre></li>
</ul>]]></description>
    <pubDate>Thu, 17 Apr 2025 08:30:04 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/network/452.html</guid>
</item>
<item>
    <title>docker端口映射不能正常访问</title>
    <link>https://xingdong365.com/network/451.html</link>
    <description><![CDATA[<p>在用docker启动nginx后，通过ip地址无法访问</p>
<pre><code class="language-shell">docker run -d --name  mynginx -p 80:80 nginx</code></pre>
<p>Linux转发是要开启内核转发功能net.ipv4.ip_forward的，于是检查内核转发是否打开</p>
<pre><code class="language-shell">[root@VM-20-17-centos ~]# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0</code></pre>
<p>这里发现内核转发是处于关闭状态的（0代表内核转发没有打开）这里手动echo命令开启内核转发，并再次查询内核转发是否开启</p>
<pre><code class="language-shell">[root@VM-20-17-centos ~]# echo 1 &gt; /proc/sys/net/ipv4/ip_forward
[root@VM-20-17-centos ~]# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1</code></pre>
<p>这个时候在通过ip访问就OK了<br />
永久生效的话 vim /etc/sysctl.conf，手动修改 ,sysctl -p 重新加载</p>]]></description>
    <pubDate>Thu, 02 Jan 2025 13:13:17 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/network/451.html</guid>
</item>
<item>
    <title>【面试】MySQL主从集群同步延迟问题怎么解决</title>
    <link>https://xingdong365.com/database/450.html</link>
    <description><![CDATA[<ol>
<li>检查优化一下主从之间的网络环境，看主从之间的网络是否存在延迟</li>
<li>查看主从数据库的资源是否有压力，能够快速处理事务请求（增加资源、降低请求量）</li>
<li>查看是否有大的事务和长耗时的操作，尽快的去避免将大事务进行拆分处理</li>
<li>可以从架构层面的一些配置去进行优化，比如修改binlog的同步机制，在性能与一致性之间做一个取舍</li>
<li>可以在业务层面去解决，比如真的对某些一致性要求非常高的读取场景，我们可以直接基于动态数据资源切换到我们主库去进行读取。</li>
</ol>]]></description>
    <pubDate>Mon, 30 Dec 2024 08:54:55 +0800</pubDate>
    <dc:creator>action</dc:creator>
    <guid>https://xingdong365.com/database/450.html</guid>
</item>
</channel>
</rss>