2026-06-25 a26b31cc9f3ee9b21b1a754e80fa7359e8a7a8f8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
package cn.iocoder.yudao.module.ai.tool.method;
 
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
 
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
/**
 * 来自 Spring AI 官方文档
 *
 * Implementation of the PersonService interface using an in-memory data store.
 * Manages a collection of Person objects loaded from embedded CSV data.
 * This class is thread-safe due to the use of ConcurrentHashMap and AtomicInteger.
 */
@Service
@Slf4j
public class PersonServiceImpl implements PersonService {
 
    private final Map<Integer, Person> personStore = new ConcurrentHashMap<>();
 
    private AtomicInteger idGenerator;
 
    /**
     * Embedded CSV data for initial population
     */
    private static final String CSV_DATA = """
            Id,FirstName,LastName,Email,Sex,IpAddress,JobTitle,Age
            1,Fons,Tollfree,ftollfree0@senate.gov,Male,55.1 Tollfree Lane,Research Associate,31
            2,Emlynne,Tabourier,etabourier1@networksolutions.com,Female,18 Tabourier Way,Associate Professor,38
            3,Shae,Johncey,sjohncey2@yellowpages.com,Male,1 Johncey Circle,Structural Analysis Engineer,30
            4,Sebastien,Bradly,sbradly3@mapquest.com,Male,2 Bradly Hill,Chief Executive Officer,40
            5,Harriott,Kitteringham,hkitteringham4@typepad.com,Female,3 Kitteringham Drive,VP Sales,47
            6,Anallise,Parradine,aparradine5@miibeian.gov.cn,Female,4 Parradine Street,Analog Circuit Design manager,44
            7,Gorden,Kirkbright,gkirkbright6@reuters.com,Male,5 Kirkbright Plaza,Senior Editor,40
            8,Veradis,Ledwitch,vledwitch7@google.com.au,Female,6 Ledwitch Avenue,Computer Systems Analyst IV,44
            9,Agnesse,Penhalurick,apenhalurick8@google.it,Female,7 Penhalurick Terrace,Automation Specialist IV,41
            10,Bibby,Hutable,bhutable9@craigslist.org,Female,8 Hutable Place,Account Representative I,43
            11,Karoly,Lightoller,klightollera@rakuten.co.jp,Female,9 Lightoller Parkway,Senior Developer,46
            12,Cristine,Durrad,cdurradb@aol.com,Female,10 Durrad Center,Senior Developer,48
            13,Aggy,Napier,anapierc@hostgator.com,Female,11 Napier Court,VP Product Management,44
            14,Prisca,Caddens,pcaddensd@vinaora.com,Female,12 Caddens Alley,Business Systems Development Analyst,41
            15,Khalil,McKernan,kmckernane@google.fr,Male,13 McKernan Pass,Engineer IV,44
            16,Lorry,MacTrusty,lmactrustyf@eventbrite.com,Male,14 MacTrusty Junction,Design Engineer,42
            17,Casandra,Worsell,cworsellg@goo.gl,Female,15 Worsell Point,Systems Administrator IV,45
            18,Ulrikaumeko,Haveline,uhavelineh@usgs.gov,Female,16 Haveline Trail,Financial Advisor,42
            19,Shurlocke,Albany,salbanyi@artisteer.com,Male,17 Albany Plaza,Software Test Engineer III,46
            20,Myrilla,Brimilcombe,mbrimilcombej@accuweather.com,Female,18 Brimilcombe Road,Programmer Analyst I,48
            21,Carlina,Scimonelli,cscimonellik@va.gov,Female,19 Scimonelli Pass,Help Desk Technician,45
            22,Tina,Goullee,tgoulleel@miibeian.gov.cn,Female,20 Goullee Crossing,Accountant IV,43
            23,Adriaens,Storek,astorekm@devhub.com,Female,21 Storek Avenue,Recruiting Manager,40
            24,Tedra,Giraudot,tgiraudotn@wiley.com,Female,22 Giraudot Terrace,Speech Pathologist,47
            25,Josiah,Soares,jsoareso@google.nl,Male,23 Soares Street,Tax Accountant,45
            26,Kayle,Gaukrodge,kgaukrodgep@wikispaces.com,Female,24 Gaukrodge Parkway,Accountant II,43
            27,Ardys,Chuter,achuterq@ustream.tv,Female,25 Chuter Drive,Engineer IV,41
            28,Francyne,Baudinet,fbaudinetr@newyorker.com,Female,26 Baudinet Center,VP Accounting,48
            29,Gerick,Bullan,gbullans@seesaa.net,Male,27 Bullan Way,Senior Financial Analyst,43
            30,Northrup,Grivori,ngrivorit@unc.edu,Male,28 Grivori Plaza,Systems Administrator I,45
            31,Town,Duguid,tduguidu@squarespace.com,Male,29 Duguid Pass,Safety Technician IV,46
            32,Pierette,Kopisch,pkopischv@google.com.br,Female,30 Kopisch Lane,Director of Sales,41
            33,Jacquenetta,Le Prevost,jleprevostw@netlog.com,Female,31 Le Prevost Trail,Senior Developer,47
            34,Garvy,Rusted,grustedx@aboutads.info,Male,32 Rusted Junction,Senior Developer,42
            35,Clarice,Aysh,cayshy@merriam-webster.com,Female,33 Aysh Avenue,VP Quality Control,40
            36,Tracie,Fedorski,tfedorskiz@bloglines.com,Male,34 Fedorski Terrace,Design Engineer,44
            37,Noelyn,Matushenko,nmatushenko10@globo.com,Female,35 Matushenko Place,VP Sales,48
            38,Rudiger,Klaesson,rklaesson11@usnews.com,Male,36 Klaesson Road,Database Administrator IV,43
            39,Mirella,Syddie,msyddie12@geocities.jp,Female,37 Syddie Circle,Geological Engineer,46
            40,Donalt,O'Lunny,dolunny13@elpais.com,Male,38 O'Lunny Center,Analog Circuit Design manager,41
            41,Guntar,Deniskevich,gdeniskevich14@google.com.hk,Male,39 Deniskevich Way,Structural Engineer,47
            42,Hort,Shufflebotham,hshufflebotham15@about.me,Male,40 Shufflebotham Court,Structural Analysis Engineer,45
            43,Dominique,Thickett,dthickett16@slashdot.org,Male,41 Thickett Crossing,Safety Technician I,42
            44,Zebulen,Piscopello,zpiscopello17@umich.edu,Male,42 Piscopello Parkway,Web Developer II,40
            45,Mellicent,Mac Giany,mmacgiany18@state.tx.us,Female,43 Mac Giany Pass,Assistant Manager,44
            46,Merle,Bounds,mbounds19@amazon.co.jp,Female,44 Bounds Alley,Systems Administrator III,41
            47,Madelle,Farbrace,mfarbrace1a@xinhuanet.com,Female,45 Farbrace Terrace,Quality Engineer,48
            48,Galvin,O'Sheeryne,gosheeryne1b@addtoany.com,Male,46 O'Sheeryne Way,Environmental Specialist,43
            49,Guillemette,Bootherstone,gbootherstone1c@nationalgeographic.com,Female,47 Bootherstone Plaza,Professor,46
            50,Letti,Aylmore,laylmore1d@vinaora.com,Female,48 Aylmore Circle,Automation Specialist I,40
            51,Nonie,Rivalland,nrivalland1e@weather.com,Female,49 Rivalland Avenue,Software Test Engineer IV,45
            52,Jacquelynn,Halfacre,jhalfacre1f@surveymonkey.com,Female,50 Halfacre Pass,Geologist II,42
            53,Anderea,MacKibbon,amackibbon1g@weibo.com,Female,51 MacKibbon Parkway,Automation Specialist II,47
            54,Wash,Klimko,wklimko1h@slashdot.org,Male,52 Klimko Alley,Database Administrator I,40
            55,Flori,Kynett,fkynett1i@auda.org.au,Female,53 Kynett Trail,Quality Control Specialist,46
            56,Libbey,Penswick,lpenswick1j@google.co.uk,Female,54 Penswick Point,VP Accounting,43
            57,Silvanus,Skellorne,sskellorne1k@booking.com,Male,55 Skellorne Drive,Account Executive,48
            58,Carmine,Mateos,cmateos1l@plala.or.jp,Male,56 Mateos Terrace,Systems Administrator I,41
            59,Sheffie,Blazewicz,sblazewicz1m@google.com.au,Male,57 Blazewicz Center,VP Sales,44
            60,Leanor,Worsnop,lworsnop1n@uol.com.br,Female,58 Worsnop Plaza,Systems Administrator III,45
            61,Caspar,Pamment,cpamment1o@google.co.jp,Male,59 Pamment Court,Senior Financial Analyst,42
            62,Justinian,Pentycost,jpentycost1p@sciencedaily.com,Male,60 Pentycost Way,Senior Quality Engineer,47
            63,Gerianne,Jarnell,gjarnell1q@bing.com,Female,61 Jarnell Avenue,Help Desk Operator,40
            64,Boycie,Zanetto,bzanetto1r@about.com,Male,62 Zanetto Place,Quality Engineer,46
            65,Camilla,Mac Giany,cmacgiany1s@state.gov,Female,63 Mac Giany Parkway,Senior Cost Accountant,43
            66,Hadlee,Piscopiello,hpiscopiello1t@artisteer.com,Male,64 Piscopiello Street,Account Representative III,48
            67,Bobbie,Penvarden,bpenvarden1u@google.cn,Male,65 Penvarden Lane,Help Desk Operator,41
            68,Ali,Gowlett,agowlett1v@parallels.com,Male,66 Gowlett Pass,VP Marketing,44
            69,Olivette,Acome,oacome1w@qq.com,Female,67 Acome Hill,VP Product Management,45
            70,Jehanna,Brotherheed,jbrotherheed1x@google.nl,Female,68 Brotherheed Junction,Database Administrator III,42
            71,Morgan,Berthomieu,mberthomieu1y@artisteer.com,Male,69 Berthomieu Alley,Systems Administrator II,47
            72,Linzy,Shilladay,lshilladay1z@icq.com,Female,70 Shilladay Trail,Research Assistant IV,40
            73,Faydra,Brimner,fbrimner20@mozilla.org,Female,71 Brimner Road,Senior Editor,46
            74,Gwenore,Oxlee,goxlee21@devhub.com,Female,72 Oxlee Terrace,Systems Administrator II,43
            75,Evangelin,Beinke,ebeinke22@mozilla.com,Female,73 Beinke Circle,Accountant I,48
            76,Missy,Cockling,mcockling23@si.edu,Female,74 Cockling Way,Software Engineer I,41
            77,Suzanne,Klimschak,sklimschak24@etsy.com,Female,75 Klimschak Plaza,Tax Accountant,44
            78,Candide,Goricke,cgoricke25@weebly.com,Female,76 Goricke Pass,Sales Associate,45
            79,Gerome,Pinsent,gpinsent26@google.com.au,Male,77 Pinsent Junction,Software Consultant,42
            80,Lezley,Mac Giany,lmacgiany27@scribd.com,Male,78 Mac Giany Alley,Operator,47
            81,Tobiah,Durn,tdurn28@state.tx.us,Male,79 Durn Court,VP Sales,40
            82,Sherlocke,Cockshoot,scockshoot29@yelp.com,Male,80 Cockshoot Street,Senior Financial Analyst,46
            83,Myrle,Speenden,mspeenden2a@utexas.edu,Female,81 Speenden Center,Senior Developer,43
            84,Isidore,Gorries,igorries2b@flavors.me,Male,82 Gorries Parkway,Sales Representative,48
            85,Isac,Kitchingman,ikitchingman2c@businessinsider.com,Male,83 Kitchingman Drive,VP Accounting,41
            86,Benedetta,Purrier,bpurrier2d@admin.ch,Female,84 Purrier Trail,VP Accounting,44
            87,Tera,Fitchell,tfitchell2e@fotki.com,Female,85 Fitchell Place,Software Engineer IV,45
            88,Abbe,Pamment,apamment2f@about.com,Male,86 Pamment Avenue,VP Sales,42
            89,Jandy,Gommowe,jgommowe2g@angelfire.com,Female,87 Gommowe Road,Financial Analyst,47
            90,Karena,Fussey,kfussey2h@google.com.au,Female,88 Fussey Point,Assistant Professor,40
            91,Gaspar,Pammenter,gpammenter2i@google.com.br,Male,89 Pammenter Hill,Help Desk Operator,46
            92,Stanwood,Mac Giany,smacgiany2j@prlog.org,Male,90 Mac Giany Terrace,Research Associate,43
            93,Byrom,Beedell,bbeedell2k@google.co.jp,Male,91 Beedell Way,VP Sales,48
            94,Annabella,Rowbottom,arowbottom2l@google.com.au,Female,92 Rowbottom Plaza,Help Desk Operator,41
            95,Rodolphe,Debell,rdebell2m@imageshack.us,Male,93 Debell Pass,Design Engineer,44
            96,Tyne,Gommey,tgommey2n@joomla.org,Female,94 Gommey Junction,VP Marketing,45
            97,Christoper,Pincked,cpincked2o@icq.com,Male,95 Pincked Alley,Human Resources Manager,42
            98,Kore,Le Prevost,kleprevost2p@tripadvisor.com,Female,96 Le Prevost Street,VP Quality Control,47
            99,Ceciley,Petrolli,cpetrolli2q@oaic.gov.au,Female,97 Petrolli Court,Senior Developer,40
            100,Elspeth,Mac Giany,emacgiany2r@icio.us,Female,98 Mac Giany Parkway,Internal Auditor,46
            """;
 
    /**
     * Initializes the service after dependency injection by loading data from the CSV string.
     * Uses @PostConstruct to ensure this runs after the bean is created.
     */
    @PostConstruct
    private void initializeData() {
        log.info("Initializing PersonService data store...");
        int maxId = loadDataFromCsv();
        idGenerator = new AtomicInteger(maxId);
        log.info("PersonService initialized with {} records. Next ID: {}", personStore.size(), idGenerator.get() + 1);
    }
 
    /**
     * Parses the embedded CSV data and populates the in-memory store.
     * Calculates the maximum ID found in the data to initialize the ID generator.
     *
     * @return The maximum ID found in the loaded CSV data.
     */
    private int loadDataFromCsv() {
        final AtomicInteger currentMaxId = new AtomicInteger(0);
        // Clear existing data before loading (important for tests or re-initialization scenarios)
        personStore.clear();
        try (Stream<String> lines = CSV_DATA.lines().skip(1)) { // Skip header row
            lines.forEach(line -> {
                try {
                    // Split carefully, handling potential commas within quoted fields if necessary (simple split here)
                    String[] fields = line.split(",", 8); // Limit split to handle potential commas in job title
                    if (fields.length == 8) {
                        int id = Integer.parseInt(fields[0].trim());
                        String firstName = fields[1].trim();
                        String lastName = fields[2].trim();
                        String email = fields[3].trim();
                        String sex = fields[4].trim();
                        String ipAddress = fields[5].trim();
                        String jobTitle = fields[6].trim();
                        int age = Integer.parseInt(fields[7].trim());
 
                        Person person = new Person(id, firstName, lastName, email, sex, ipAddress, jobTitle, age);
                        personStore.put(id, person);
                        currentMaxId.updateAndGet(max -> Math.max(max, id));
                    } else {
                        log.warn("Skipping malformed CSV line (expected 8 fields, found {}): {}", fields.length, line);
                    }
                } catch (NumberFormatException e) {
                    log.warn("Skipping line due to parsing error (ID or Age): {} - Error: {}", line, e.getMessage());
                } catch (Exception e) {
                    log.error("Skipping line due to unexpected error: {} - Error: {}", line, e.getMessage(), e);
                }
            });
        } catch (Exception e) {
            log.error("Fatal error reading embedded CSV data: {}", e.getMessage(), e);
            // In a real application, might throw a specific initialization exception
        }
        return currentMaxId.get();
    }
 
    @Override
    @Tool(
        name = "ps_create_person",
        description = "Create a new person record in the in-memory store."
    )
    public Person createPerson(Person personData) {
        if (personData == null) {
            throw new IllegalArgumentException("Person data cannot be null");
        }
        int newId = idGenerator.incrementAndGet();
        // Create a new Person record using data from the input, but with the generated ID
        Person newPerson = new Person(
                newId,
                personData.firstName(),
                personData.lastName(),
                personData.email(),
                personData.sex(),
                personData.ipAddress(),
                personData.jobTitle(),
                personData.age()
        );
        personStore.put(newId, newPerson);
        log.debug("Created person: {}", newPerson);
        return newPerson;
    }
 
    @Override
    @Tool(
        name = "ps_get_person_by_id",
        description = "Retrieve a person record by ID from the in-memory store."
    )
    public Optional<Person> getPersonById(int id) {
        Person person = personStore.get(id);
        log.debug("Retrieved person by ID {}: {}", id, person);
        return Optional.ofNullable(person);
    }
 
    @Override
    @Tool(
        name = "ps_get_all_persons",
        description = "Retrieve all person records from the in-memory store."
    )
    public List<Person> getAllPersons() {
        // Return an unmodifiable view of the values
        List<Person> allPersons = personStore.values().stream().toList();
        log.debug("Retrieved all persons (count: {})", allPersons.size());
        return allPersons;
    }
 
    @Override
    @Tool(
        name = "ps_update_person",
        description = "Update an existing person record by ID in the in-memory store."
    )
    public boolean updatePerson(int id, Person updatedPersonData) {
         if (updatedPersonData == null) {
            throw new IllegalArgumentException("Updated person data cannot be null");
        }
        // Use computeIfPresent for atomic update if the key exists
        Person result = personStore.computeIfPresent(id, (key, existingPerson) ->
                // Create a new Person record with the original ID but updated data
                new Person(
                        id, // Keep original ID
                        updatedPersonData.firstName(),
                        updatedPersonData.lastName(),
                        updatedPersonData.email(),
                        updatedPersonData.sex(),
                        updatedPersonData.ipAddress(),
                        updatedPersonData.jobTitle(),
                        updatedPersonData.age()
                )
        );
        boolean updated = result != null;
        log.debug("Update attempt for ID {}: {}", id, updated ? "Successful" : "Failed (Not Found)");
        if(updated) log.trace("Updated person data for ID {}: {}", id, result);
        return updated;
    }
 
    @Override
    @Tool(
        name = "ps_delete_person",
        description = "Delete a person record by ID from the in-memory store."
    )
    public boolean deletePerson(int id) {
        boolean removed = personStore.remove(id) != null;
        log.debug("Delete attempt for ID {}: {}", id, removed ? "Successful" : "Failed (Not Found)");
        return removed;
    }
 
    @Override
    @Tool(
        name = "ps_search_by_job_title",
        description = "Search for persons by job title in the in-memory store."
    )
    public List<Person> searchByJobTitle(String jobTitleQuery) {
        if (jobTitleQuery == null || jobTitleQuery.isBlank()) {
            log.debug("Search by job title skipped due to blank query.");
            return Collections.emptyList();
        }
        String lowerCaseQuery = jobTitleQuery.toLowerCase();
        List<Person> results = personStore.values().stream()
                .filter(person -> person.jobTitle() != null && person.jobTitle().toLowerCase().contains(lowerCaseQuery))
                .collect(Collectors.toList());
        log.debug("Search by job title '{}' found {} results.", jobTitleQuery, results.size());
        return Collections.unmodifiableList(results);
    }
 
    @Override
    @Tool(
        name = "ps_filter_by_sex",
        description = "Filters Persons by sex (case-insensitive)."
    )
    public List<Person> filterBySex(String sex) {
        if (sex == null || sex.isBlank()) {
             log.debug("Filter by sex skipped due to blank filter.");
            return Collections.emptyList();
        }
        List<Person> results = personStore.values().stream()
                .filter(person -> person.sex() != null && person.sex().equalsIgnoreCase(sex))
                .collect(Collectors.toList());
        log.debug("Filter by sex '{}' found {} results.", sex, results.size());
        return Collections.unmodifiableList(results);
    }
 
    @Override
    @Tool(
        name = "ps_filter_by_age",
        description = "Filters Persons by age."
    )
    public List<Person> filterByAge(int age) {
         if (age < 0) {
            log.debug("Filter by age skipped due to negative age: {}", age);
            return Collections.emptyList(); // Or throw IllegalArgumentException based on requirements
        }
        List<Person> results = personStore.values().stream()
                .filter(person -> person.age() == age)
                .collect(Collectors.toList());
        log.debug("Filter by age {} found {} results.", age, results.size());
        return Collections.unmodifiableList(results);
    }
 
}